SuperCollider日記(1)

長嶋 洋一

(一部 Processing日記かも)

 Part 2   Part 3   Part 4   Part 5   Part 6   Part 7 


2011年2月24日(木)

マルチメディア室のコンピュータが新しくなる節目の2011年2月23日、 こんな作業 をやっていて、あと42台も同じ作業があるのか・・・とウンザリしたが、ちゃんとその中に SuperColliderProcessing を入れていたのを思い出し、フト、翌日の2011年2月24日、思い立った。 そういえば、これらのソフトはほとんど手つかずだったのだ(^_^;)。

SuperCollider は、まだMacOS7.5とか8.6の時代に、 (旧)SuperCollider を使ってパフォーマンスするところまでやっていたが、その後は Kyma を使うこともなくなるとともに、もっぱら Max/MSP/jitter だけになってしまっていた。

そこで今回、新学期には、最近レベルが上がってきた学生に、 Max/MSP/jitter だけでなく、 SuperColliderProcessing も紹介して、ごく一部の学生であっても、これらを活用できるようにしよう、と決意した。 明日は一般前期入試だし、いろいろあって時間は限られているが、せっかくなので、 春休みの期間を活用して、両方を同時に並行して勉強してみよう。

昼過ぎまで教授会や委員会があったが、翌日が入試ということで午後は入構制限となって静かになったので、 まずは環境設定から開始した。 まずは SuperCollider のサイトに行った。 ダウンロードページ から、「SuperCollider3.4-with-extras-10.6」をダウンして解凍してみると、以下のようになった。

これで、左端のアイコンをその右のアイコンに落とせばアプリ本体のインストールは完了である。 しかし右側の「Optional Installs」はちょっと困った。この中身は以下のようになっている。

そしてこの中の「README.rtf」を読むと、以下のようになっていた。 さすが、歴史があるだけに、いろいろ拡張されているようで、ややこしい(^_^;)。 とりあえず、言われるように、「quarks」と「sc3-plugins」と「SwingOSC」とを、指定された場所にフォルダを作ってコピーした。

これで終わりかというと、そうでもない(^_^;)。 SuperCollider の「Download」のところには、 「Also download plugins and extensions to enhance SuperCollider.」とあり、 以下のような図とリンクがまたまた登場した。

「SuperCollider plugins provide additional synthesis (and other) capabilities to the server - i.e. new types of sound source, filter, etc.」とあるので、とりあえずこれらのプラグインを入れない手はないので、リンク先の ここ から「SC3ExtPlugins2010-09-03.dmg」をダウンロードして解凍すると以下のようなものがあった。 この中身を、たぶんdefaultでパスが通っていると信じて、「plugins」フォルダの下にコピーした。

Extensionsについては、「SuperCollider extensions are classes or class libraries that provide additional functionality for use in the object-oriented language.」とあり、 「Quarks is the name for the SuperCollider “package-management” system, i.e. a system for automatically installing/updating SuperCollider class extensions written by users. Check out the current list of Quarks to see what’s available.」 とのことなので、とりあえず Quarks for SuperCollider に行ってみると、膨大なリストに閉口した。 ここはまたいずれ来るとして、とりあえずパスすることにしよう(^_^;)。

SuperCollider の「Audio/code examples」のところには、 たくさんのサンプルコード があり、さらに「Learning」のところには、 YouTubeのデモ へのリンクとともに、 チュートリアル があった。 YouTubeの「1分間でSuperColliderを紹介する」というビデオは強力である。 当面、これらに従って順におさらいしていけばOKだろう。

この、 チュートリアル のリンクの中に、日本語のページがある、という情報を発見した。 たぶんタケコさんのところだろう・・・と辿っていくと、 SuperColliderとProcessingとの連携 というページがあった。 なるほど、確かにOSCとか使えばSuperColliderはサーバなので容易に連携できそうだ。 そうなると、OSCで全体をまとめれば、

というのも簡単そうである。これはいいカモ。(^_^)

さて、せっかくなので、 チュートリアル のページに行ってみた。 さすがに歴史のあるSuperColliderである、「Recommended introductory tutorials」 というだけで、たくさんあった。 これ は、ダウンロードしたパッケージに入っているもので、さらに続編として これ もあるという。
また、 これ は、音響合成・音響処理に関するワークショップの全コースをSuperColliderで行ったもののテキストのようで、これでほぼSuperColliderで出来ることを概観できそうな力作である。
もう1件の これ はNot Foundになっていた。ユタ大学での講義資料だったのかもしれない。

そこで、とりあえず、まずは これ に従ってみることにした。 最初はもちろん「Hello World」である。 とりあえずSuperColliderのアプリのアイコンをダブルクリックすると、昔のSuperColliderと同じようなテキストウインドウと、見慣れない2つのGUIウインドウが現れた。 そこにコピペで


"Hello World!".postln;
と入れて、リターンキーを叩いたが何も起きなかった(^_^;)。 そう、SuperColliderではリターンキーとは別に、キーボードの端にある「enter」キーが特別の役割を担っているのだった。 そこで「enter」を叩くと、以下のように出た。これがSuperColliderの「Hello World」である。

「enter」キーに相当する機能は、「control+リターン」「shift+リターン」でもOKである。 また、複数行にわたるプログラムを実行させるには、例えば


"Hello World!".postln;
"Hello SC!".postln;
という2行を実行させるためには、 のいずれでも良い。後の2つの場合、ソースの見た目は

(
"Hello World!".postln;
"Hello SC!".postln;
)
とか

("Hello World!".postln;
"Hello SC!".postln;)
とか

(
	"Hello World!".postln;
	"Hello SC!".postln;
)
になるが、いずれもOKである。 なお、後では中カッコ { } とかカギカッコ [ ] が主役になるので注意が必要である。

昔のSuperColliderであれば、単独のアプリだったので、ここでサウンドを記述する1行を入れて「enter」キーが出たが、SuperCollider3になってサーバシステムとなったので、まず準備が必要になった。 謎だった、以下のウインドウの出番である。

「localhost server」とは、ネットワークの向こうにある他のコンピュータに対する「自分」のことである。 SuperColliderでは、サウンドを鳴らすコンピュータが自分であるのとネットワークの向こうにあるのとを同等に扱える。 ここで「Boot」ボタンで起動させると、あれこれメッセージが表示されて、以下のようにボタンが緑色「Quit」になる。

このlocalhost serverは、テキストウインドウからのコマンドで


s.boot;
としても

Server.local.boot;
としても起動できる。終了は

s.quit;
あるいは

Server.local.quit;
である。 これでようやくサウンドが鳴る環境が出来た。localhost serverを走らせて、次に進もう。

テキストは次のページに進んで これ になった。 この最初にある


{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;
というプログラムをコピペして「enter」キーにより、2ヘルツの唸りのある440Hzのサインが鳴り出し、ずっと続いた。 こんな音である。

ただし、チュートルアルはこの後、関数 Funcction の解説が延々と続くので、サウンドはしばしお預けとなった。 ここは、


f = { arg a; a.value + 3 }; // call 'value' on the arg; polymorphism awaits!
f.value(3); // 3.value = 3, so this returns 3 + 3 = 6
g = { 3.0.rand; };
f.value(g); // here the arg is a Function. Cool, huh?
f.value(g); // try it again, different result
というようにあまり面白くないので(^_^;)、軽くスルーすることにした。

この次のページでは、


{ SinOsc.ar(440, 0, 0.2) }.play;
を鳴らしつつ、音のパラメータとして、ピッチと位相と音量を解説している。 Max/MSPではサウンドの位相はあまり考えていなかったが、SuperColliderではきちんと指定できるのが強力である。 上記は

(
	{ 			// Open the Function
		SinOsc.ar( 	// Make an audio rate SinOsc
		440, 		// frequency of 440 Hz, or the tuning A
		0, 		// initial phase of 0, or the beginning of the cycle
		0.2) 		// mul of 0.2
	}.play; 		// close the Function and call 'play' on it
)
とすることで意味を把握できる。ここまでは簡単なのでサッサと読み流した。 そしてこのセクションの最後は、

(
	{
		var ampOsc;
		ampOsc = SinOsc.kr(0.5, 1.5pi, 0.5, 0.5);
		SinOsc.ar(440, 0, ampOsc);
	}.play;
)
ということで、振幅変調(AM)のかかったサインとなった。 こんな音である。 localhost serverパネルにあった「record」ボタンで、このように簡単に鳴っているサウンドがレコーディングできるので、色々に活用できそうである。

2011年2月25日(金)

今日は、SUACとしては初めてとなる「国公立入試」の日なので、時間はほとんど取れず、何も進まず。(^_^;)

ただし、 SuperCollider のページにあるリンクからたどり着いた、 SuperCollider Osaka Meetup のページに、無謀にも参加登録してみたが、ここで参加する クラフトワイフ というところに、なんと、麗しきタケコさん発見(^_^)。 これは楽しみになった。 というか、ちゃんとモノにしないと。(^_^;)

2011年2月26日(土)

この日は、午前中に Processing日記 をちょっとだけ進めたので、午後はSuperColliderも進めてみることにした。 時間のあるときに少しでも進める、というのが肝心だ。

チュートリアルを巡っているProcessingと同様に、SuperColliderでも チュートリアルこれ の続きを進めてみることになる。 前回、「Functions and Other Functionality」と「Functions and Sound」をやったので、その次のトピックは Presented in Living Stereo である。

まずは、あらためて


{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;
からスタートである。 実行させると、440Hzと442Hzのサイン波の唸りが気持ちよく鳴っている。 ここでの解説では、「中カッコ { } は関数」そして「四角カッコ [ ] は(Objectsの)配列」である、と強調している。 「四角カッコ [ ] 」の中でコンマで区切られて列記されているのがオブジェクトである。 SuperColliderではあらゆるものがObjectであり、その集合もまたObjectである。 これが、シンプルでも複雑なサウンドを生み出すSuperColliderの肝の部分だという。

配列の定義と参照は、以下のようによくあるスタイルである。


a = ["foo", "bar"]; // "foo" is at index 0; "bar" is at index 1
a.at(0);
a.at(1);
a.at(2); // returns "nil", as there is no object at index 2
// there's a shorthand for at that you'll see sometimes:
a[0]; // same as a.at(0);
これは多くの処理系で見かけるが、SuperColliderにおいては、

{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;
のようにサウンドをplayする局面では、この2つの要素がleftとrightのステレオサウンドチャンネルに対応するという。 もっと配列の要素を多くした場合には、簡単に多チャンネルオーディオとなるらしいが、出力デバイスが無いと検証できない。

そして面白いのが、上記のプログラムは以下のようにも書けるという。


{ SinOsc.ar([440, 442], 0, 0.2) }.play;
これはなかなかトリッキーだが、より簡潔に複雑な構造を記述できそうな気がする。

次に提示されたサンプル


(
	{
		var freq;
		freq = [[660, 880], [440, 660], 1320, 880].choose;
		SinOsc.ar(freq, 0, 0.2); 
	}.play;
)
を何度か実行してみると、「choose」は配列の要素からランダムに選ぶということで、刻々と違うサウンドが加わって鳴る。 そして、コンマで切った2つの数値の時には左右で違う音が鳴り(ステレオ)、1つの数値の時にはモノラルになる。 これで複雑なことが簡単に出来そうである。

先日はAM(振幅変調)をやっていたが、ここでもう1つ、パンニング「Pan2」が登場した。 たった1行の


{ Pan2.ar(PinkNoise.ar(0.2), SinOsc.kr(0.5)) }.play;
を実行させたサウンドが こんな音である。 つまり、「Pan2.ar」の「.ar」はオーディオレート、すなわちサウンド領域であり、その第1引数はサウンドソースのようだ。 ここではピンクノイズである。 そして第2引数はパンニングのパラメータで、「SinOsc.kr(0.5)」とサインでゆっくり動く。 どうやら「.kr」は、詳細不明ながらサウンド領域よりも低速の変化パラメータを指定するらしい。

これで「Presented in Living Stereo」は終わったので、その次の Mix it Up に進むことにした。 今度は同じチャンネルで複数のサウンドを足し合わせる、ということになる。 Max/MSPではサウンドのオブジェクトからの「黄色と黒の紐」をどんどん合流させればサウンドがmixされたが、SuperColliderでも簡単であった。 最初のサンプル


{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.play;
を実行させてみると、 こんな音である。 これはソースを見れば一目瞭然、ピンクノイズと440Hzサインとその完全5度上の鋸歯状波のミックスである。 それぞれの振幅を「0.2」と小さくすることで、最終的なサウンド出力が-1から1までの間に入って、 クリップしないようにしているわけである。 試しに

{ PinkNoise.ar(0.5) + SinOsc.ar(440, 0, 0.5) + Saw.ar(660, 0.5) }.play;
とすると、音量増加だけでなく割れたような音になった。 おそらく自動クリッピングは無いので(???)、ここは注意が必要だろう。

次に登場するのは「Mix」という新しいクラスである。これは配列として指定されたチャンネルを1チャンネルにミックスする、あるいは複数の配列をチャンネルの配列にミックスするものである。 「.postln」で起動されたUnit Generatorの様子がpostウインドウに表示される、ということで、


{ Mix.new([SinOsc.ar(440, 0, 0.2), Saw.ar(660, 0.2)]).postln }.play;
を実行させてみると、確かにモノラルで こんな音である。 postウインドウの表示は以下のようになった。

これに対して、


(
	{ 
		var a, b;
		a = [SinOsc.ar(440, 0, 0.2), Saw.ar(662, 0.2)];
		b = [SinOsc.ar(442, 0, 0.2), Saw.ar(660, 0.2)];
		Mix([a, b]).postln;
	}.play;
)
を実行させてみると、確かにステレオで こんな音になった。 また、postウインドウの表示も、以下のように配列でのステレオ表示となった。

このプログラムでの、「Mix.new」という「.new」というのは、新しいオブジェクトを呼び出すクラスであるが、実は短縮形としては、「Mix」でも動く、ということであった。 実際に、両者それぞれとも、「.new」を付けても省略しても同じ動作だった。

省略しても同じだから不要、というわけではなくて、Mixには他に「Mix.fill」というクラスもある。 その例示として


(
	var n = 8;
	{ Mix.fill(n, { SinOsc.ar(500 + 500.0.rand, 0, 1 / n) })  }.play;
)
を実行させてみると、 こんな音になった。 実質的に1行でサインぐらいしか見当たらないシンプルな記述なのに、である。 このプログラムでは、nが8ということで、ピッチにランダム要素を加味した8個のサインが再帰的にミックスされているのである。

この様子を、postウインドウに状況を表示するサンプル


(
	var n = 8;
	{ 
		Mix.fill(n, 
			{
				arg index; 
				var freq;
				index.postln;
				freq = 440 + index;
				freq.postln;
				SinOsc.ar(freq , 0, 1 / n) 
			}
		)
	}.play;
)
で見てみると、実行したサウンドは こんな音で、 postウインドウの表示は以下のようになった。 なるほど、nが8、と指定すると、ちゃんとn=0からn=7までやっている。

これで「Mix it Up」は終わったので、その次の Scoping Out Some Plots に進もう。 これはサウンドのビジュアライズ(波形を表示)というトピックのようである。


{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.plot;
を実行してみると、「play」でサウンドが鳴る代わりに、「plot」によって以下のように表示された。

このデフォルトの時間幅は「0.01」、すなわち10msecとのことである。 明示的に秒単位で指定するサンプルとして


{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.plot(1);
以下のように表示された。

・・・ここまで快調に来たが、その次にあった「internal server」を使った「scope」というのが、何故かうまく出なくなった。 SuperColliderを再起動しても状況は変わらない。 ここでMacをリブートするのもナンなので、潔く、今日はここまで、とした(^_^;)。 また次回、この続きからやってみよう。

2011年2月27日(日)

昨日の続きで「internal server」を使った「scope」というのを試してみたが、やはり何も起きない。 以下のように、いつも「booting」で止まったままになる。 まぁ、別に波形がscopeで見えなくても困らないので、出来ないことはサッサと諦めて、次に進もう。

次は、 Getting StartedGetting Help である。 とりあえずヘルプは ここ のようで、「Getting Help」「Tutorials」「Overviews」「Language Reference」「Architecture」「Extending SC」「Third Party Libraries」「Legacy SuperCollider」の8つのトピックの、膨大なヘルプへのリンクが並んでいる。 とりあえずは、ここをブックマークした。

さらに「Getting Help」のページでは、オブジェクト指向言語であるSuperColliderについて、「Classes and Methods」という解説がある。 ここには、登場する色々なクラスのヘルプを呼び出すには「Cmd - d」だ、と書いてあるが、どうもうまく出なかった。 ただし、postウインドウ内に置いたクラス名をダブルクリックなどでハイライトさせて、メニューの「ヘルプ」から「SuperCollider Help」を指定すると、それぞれのヘルプが以下のように出ることがわかった。これで十分だろう。

続く「Syntax Shortcuts」では、 ここ を紹介しつつ、以下の2つは同等である、と解説している。 このあたりも、JavaでもFlashのActionScriptでも同様なので、軽く読み流した。


{ SinOsc.ar(440, 0, 0.2) }.play;

play({ SinOsc.ar(440, 0, 0.2) });

この後には「Snooping, etc.」というトピックもあったが、どうもきちんと探っていくとかなり深そうなので敬遠して(^_^;)、 Getting StartedGetting SynthDefs and Synths に進んだ。 なかなかに英語の勉強となったが、どうやらサーバベースになったSuperColliderでは、これまでのように関数でサウンドを生成するだけでなく、バイトコードとしてサウンド生成のアルゴリズムをサーバに送るように定義することでも、同等な機能を実現できるらしい。 以下の2つは同等だ、ということである。


{ SinOsc.ar(440, 0, 0.2) }.play;

SynthDef.new("test", { Out.ar(0, SinOsc.ar(440, 0, 0.2)) }).play;

この両者の違いが、続いて「SynthDefs vs. Functions」に解説されている。 上の2つのプログラムの違いとしては、後者では「Out」というクラスが登場しているが、これはサーバがサウンドを出力するバスへの出力で、いわばミキサーチャンネルのようなものだという。 「Out」の最初の引数はサウンドチャンネルで、「0」だと左チャンネルらしい。たぶん「1」で右チャンネルなのかな。 第2の引数が「UGen」でなく「an Array of UGens」であれば、0を指定しても配列のチャンネルに対応して自動でインクリメントされる。 以下の例では左から440Hz、右から442Hzが出る。サウンドは こんな音である。


(
	SynthDef.new(
		"tutorial-SinOsc-stereo", 
		{
			var outArray;
			outArray = [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)];
			Out.ar(0, outArray) 
		}
	).play;
)

関数であってもSynthDefであっても、これまではサウンドを止めるにはいちいちlocalhost serverを止めていたが、これを止めずに発音を止めてリソースを解放するのが「free」である。 つまり、


x = { SinOsc.ar(660, 0, 0.2) }.play;

y = SynthDef.new("tutorial-SinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.2)) }).play;
のいずれに対しても、それぞれ以下を入れてやると、サーバが止まらずに発音が停止する。これは便利である。

x.free; // free just x

y.free; // free just y

オブジェクト指向を活用するために、関数よりもSynthDefの方がメリットがあるのは、以下のような例である。 上段の関数の場合には、呼ばれるたびに関数の演算を行うが、中段のSynthDefの場合には、最初にバイトコードとして呼ばれた時に一度だけしか演算を行わないので効率的なのだという。ただしこれではRandomと言いながら毎回、同じである(^_^;)。そこで下段のSynthDefのようにすると、今度はちゃんとランダムになりながら、処理としては軽くなる。


// first with a Function. Note the random frequency each time 'play' is called.
f = { SinOsc.ar(440 + 200.rand, 0, 0.2) };
x = f.play;
y = f.play;
z = f.play;
x.free; y.free; z.free;

// Now with a SynthDef. No randomness!
SynthDef("tutorial-NoRand", { Out.ar(0, SinOsc.ar(440 + 200.rand, 0, 0.2)) }).send(s);
x = Synth("tutorial-NoRand");
y = Synth("tutorial-NoRand");
z = Synth("tutorial-NoRand");
x.free; y.free; z.free;


// With Rand, it works!
SynthDef("tutorial-Rand", { Out.ar(0, SinOsc.ar(Rand(440, 660), 0, 0.2)) }).send(s);
x = Synth("tutorial-Rand");
y = Synth("tutorial-Rand");
z = Synth("tutorial-Rand");
x.free; y.free; z.free;

関数と違って軽いSynthDefにおいて、より細やかに引数のバラエティを与えるためには、以下のようにすればよい。 以下の場合、定義部分にアーギュメントとして「freq」と「out」というのを定義しておき、メソッドの呼び出し時にこれらを指定できるのであった。


(
	SynthDef(
		"tutorial-args", {
			arg freq = 440, out = 0; 
			Out.ar(out, SinOsc.ar(freq, 0, 0.2)); 
		}
	).send(s);
)

x = Synth("tutorial-args"); // no args, so default values
y = Synth("tutorial-args", ["freq", 660]); // change freq
z = Synth("tutorial-args", ["freq", 880, "out", 1]); // change freq and output channel

x.free; y.free; z.free;

続いて、 Getting StartedBusses に進んだ。 ざっと読んだところでは、仮想的なチャンネルとして、サウンド出力、サウンド入力、コントロール用などの「バス」を定義して、自在にワイヤリングする、というアイデアのようである。ディジタルミキサであれぱ、よくある構造である。 すでに「Out」クラスは登場しているので、ここでまず出て来るのは、以下のオーディオ入力の「In」クラスである。 2つの引数は、インデックスと、読み込む入力サウンドのチャンネル数である。後者は1以上でなければならない。 この「OutputProxy」とは特別なタイプの「UGen」のことで、SuperColliderが内部的に使うので気にするな(^_^;)、とのことである。


In.ar(0, 1); // this will return 'an OutputProxy'

In.ar(0, 4); // this will return an Array of 4 OutputProxies

「In」にも「Out」にも、「.ar」と「.kr」とがある。「.ar」はオーディオレートの信号で、「.kr」はコントロールレートまでダウンサンプリングしたものである。「.kr」を「play」しようとするとエラーが出る。 以下の例のように、同じバスに複数のSynthsを起動した場合には、ミックスされる。 ここらはMax/MSPと同じようなものである。


(
	SynthDef(
		"tutorial-args", 
		{
			arg freq = 440, out = 0; 
		Out.ar(out, SinOsc.ar(freq, 0, 0.2)); 
		}
	).send(s);
)

// both write to bus 1, and their output is mixed
x = Synth("tutorial-args", ["out", 1, "freq", 660]);

y = Synth("tutorial-args", ["out", 1, "freq", 770]);

バスの生成は以下のようになる。コントロールとオーディオの2種類がある。 バスのアロケーションはSuperColliderの方で自動でやってくれるそうなので、あまり気にしなくていいらしい。 バスの解放もまた、「free」である。


b = Bus.control(s, 2); // Get a two channel control Bus

c = Bus.audio(s); // Get a one channel private audio Bus (one is the default)

b.free; // free the indices. You can't use this Bus object after that 
c.free; 

まだ色々と書かれていたが、とりあえず「Busses in Action」に2つのサンプルがあったので、これを走らせて様子を見た。 まず最初のサンプルは以下である。 まず、最初の「( )」グループをハイライトしてenterで送るとSynthDefとバスが定義され(メソッド)、次の「( )」グループをハイライトしてenterで送ると実際にインスタンスが生成されてサウンドが鳴る。 こんな音である。 サウンドを消す時には最後の行末にカーソルを置いてenterである。


(
	SynthDef(
		"tutorial-Infreq", 
		{
			arg bus, freqOffset = 0;
			// this will add freqOffset to whatever is read in from the bus
			Out.ar(0, SinOsc.ar(In.kr(bus) + freqOffset, 0, 0.5));
		}
	).send(s);

	SynthDef(
		"tutorial-Outfreq", 
		{
			arg freq = 400, bus;
			Out.kr(bus, SinOsc.kr(1, 0, freq/40, freq));
		}
	).send(s);

	b = Bus.control(s,1);
)

(
	x = Synth.new("tutorial-Outfreq", [\bus, b]);
	y = Synth.after(x, "tutorial-Infreq", [\bus, b]);
	z = Synth.after(x, "tutorial-Infreq", [\bus, b, \freqOffset, 200]);
)

x.free; y.free; z.free; b.free;

なお、上のサンプルのソースに登場したバックスラッシュであるが、これは引用符と等価である、と確認した。 つまり、「"something"」と「\something」とは同じである、という事である。 これは今後も混在して登場するようなので注意が必要だ。

ちょっと長いが、最後のサンプルは以下である。 まず、最初の「( )」グループをハイライトしてenterで送るとSynthDefとバスが定義され(メソッド)、次の「( )」グループをハイライトしてenterで送ると実際にインスタンスが生成されてサウンドが鳴る。 ピンクノイズとサインにそれぞれリバーブがかかった、 こんな音である。 「Change the balance of wet to dry」に続く1行目の行末にカーソルを置いてenterすると、ピンクノイズのリバーブが消えて、 こんな音になる。 これに続く2行目の行末にカーソルを置いてenterすると、さらにサインのリバーブが消えて、 こんな音になる。 サウンドを消す時には最後の行末にカーソルを置いてenterである。


(
	// the arg direct will control the proportion of direct to processed signal
	SynthDef(
		"tutorial-DecayPink", 
		{
			arg outBus = 0, effectBus, direct = 0.5;
			var source;
			// Decaying pulses of PinkNoise. We'll add reverb later.
			source = Decay2.ar(Impulse.ar(1, 0.25), 0.01, 0.2, PinkNoise.ar);
			// this will be our main output
			Out.ar(outBus, source * direct);
			// this will be our effects output
			Out.ar(effectBus, source * (1 - direct));
		}
	).send(s);

	SynthDef(
		"tutorial-DecaySin",
		{
			arg outBus = 0, effectBus, direct = 0.5;
		var source;
		// Decaying pulses of a modulating Sine wave. We'll add reverb later.
		source = Decay2.ar(
			Impulse.ar(0.3, 0.25), 0.3, 1, SinOsc.ar(SinOsc.kr(0.2, 0, 110, 440))
		);
		// this will be our main output
		Out.ar(outBus, source * direct);
		// this will be our effects output
		Out.ar(effectBus, source * (1 - direct));
		}
	).send(s);

	SynthDef(
		"tutorial-Reverb", 
		{
			arg outBus = 0, inBus;
		var input;
		input = In.ar(inBus, 1);
		// a low rent reverb
		// aNumber.do will evaluate it's function argument 
		//a corresponding number of times
		// {}.dup(n) will evaluate the function n times, 
		//and return an Array of the results
		// The default for n is 2, so this makes a stereo reverb
		16.do(
			{
				input = AllpassC.ar(
					input, 0.04, {
						Rand(0.001,0.04)
					}.dup, 3
				)
			}
		);
		Out.ar(outBus, input);
		}
	).send(s);

	b = Bus.audio(s,1); // this will be our effects bus
)

(
	x = Synth.new("tutorial-Reverb", [\inBus, b]);
	y = Synth.before(x, "tutorial-DecayPink", [\effectBus, b]);
	z = Synth.before(x, "tutorial-DecaySin", [\effectBus, b, \outBus, 1]);
)

// Change the balance of wet to dry
y.set(\direct, 1); // only direct PinkNoise

z.set(\direct, 1); // only direct Sine wave

y.set(\direct, 0); // only reverberated PinkNoise

z.set(\direct, 0); // only reverberated Sine wave

x.free; y.free; z.free; b.free;

ちょっと長くなってきたが、さすがにSuperColliderらしい、いい音がした。これは楽しみだ。 ちなみに、これを鳴らしている最中に、メイル到着を知らせるメイラのデーモンがサウンドファイルを鳴らそうとしたらしく、途中でSuperColliderからのサウンドが変化してしまった。SuperColliderのサウンド生成サーバも、よりpriorityの高いプロセスとサウンドリソースの取り合いとなれば負けることが判ったので、実際にサウンド生成する作品などの場合には注意することにしよう。

2011年3月1日(火)

昨日は、午前に こんな作業 をして、午後には歯科に行ったり Processing日記 をちょっとだけ進めたりしていたので、SuperColliderは出来なかった。 今日は午後に自主制作の学生がハンダ付けに来る予定もあるので、SuperColliderから始めることにした。 Getting StartedGroups からである。

SuperColliderのサーバは、nodesと呼ばれるSynthの複合体であり、groupというのはオブジェクト指向的にそれらをまとめたものらしい。 groupによって、制御の順序や同時に送るメッセージを簡単に扱えると言う。 しかし、これに続く以下の例は、いまいちピンと来なかった。(^_^;)


g = Group.new;
h = Group.before(g);

g.free; h.free;

引き続き、以下のような例で考えてみよう、とある。 まずはlocal serverをbootして、


(
	// a stereo version
	SynthDef(
		\tutorial_DecaySin2, 
		{
			arg outBus = 0, effectBus, direct = 0.5, freq = 440;
			var source;
			// 1.0.rand2 returns a random number 
			//from -1 to 1, used here for a random pan
			source = Pan2.ar(
				Decay2.ar(
					Impulse.ar(Rand(0.3, 1), 0, 0.125), 0.3, 1, 
					SinOsc.ar(SinOsc.kr(0.2, 0, 110, freq))
				), Rand(-1.0, 1.0)
			);
			Out.ar(outBus, source * direct);
			Out.ar(effectBus, source * (1 - direct));
		}
	).send(s);

	SynthDef(
		\tutorial_Reverb2, 
		{
			arg outBus = 0, inBus;
			var input;
			input = In.ar(inBus, 2);
			16.do(
				{
					input = AllpassC.ar(
						input, 0.04, Rand(0.001,0.04), 3
					)
				}
			);
			Out.ar(outBus, input);
		}
	).send(s);
)
をenterすると、post windowには「a SynthDef」 と表示された。 続いて、

// now we create groups for effects and synths
(
	~sources = Group.new;
	~effects = Group.after(~sources); // make sure it's after
	~bus = Bus.audio(s, 2); // this will be our stereo effects bus
)
をenterした。これでグループ化したところ、post windowには「Bus(audio, 16, 2, localhost)」 と表示された。 続いて、

// now synths in the groups. The default addAction is \addToHead
(
	x = Synth(\tutorial_Reverb2, [\inBus, b], ~effects);
	y = Synth(\tutorial_DecaySin2, [\effectBus, ~bus, \outBus, 0], ~sources);
	z = Synth(\tutorial_DecaySin2, [\effectBus, ~bus, \outBus, 0, \freq, 660], ~sources);
)
をenterした。これでSynthが実行されて、post windowには「Synth("tutorial_DecaySin2" : 1010)」 と表示され、 こんな音が出た。 SuperColliderらしい、いい音である(^_^)。 このサウンドは、サーバを止めずに

~sources.free;
~effects.free;
~bus.free;
で止めることが出来る。1行目でサウンドが止まり、3行目で「Bus(audio, nil, nil, localhost)」 と表示された。 さらにgroupの参照をクリアするために、

// remove references to ~sources and ~effects environment variables:
currentEnvironment.clear;
をenterすると、「Environment[ ]」と表示された。 これは重要で、色々と設定した環境をクリアしないと、次第にゴミが溜まってメモリを食うので、これは必須となる。 また、このEnvironmentをnewでcreateして、それを後で復活させるためには、以下があるという。 やはりpushとpopは健在なのだった。(^_^;)

// to be sure, create a new Environment:
Environment.new.push;

// some code..

// restore old environment
currentEnvironment.pop;
これに続いて、addActionsとして、「addBefore」、「addAfter」、「addReplace」、「addToHead」、「addToTail」などが、以下のサンプルとともに書かれていたが、どうもここもビンと来ないのでさりげにスルーした。(^_^;)

g = Group.new;
h = Group.head(g); // add h to the head of g
x = Synth.tail(h, \default); // add x to the tail of h
s.queryAllNodes; // this will post a representation of the node hierarchy
x.free; h.free; g.free;
この「queryAllNodes」というメソッドは、post windowに出て来る「a Server」「Group(1001)」とか「Synth 1002」などのメッセージに関係している。 サーバが起動されると、ID=0である特別なRootノードが起動され、そこからツリー構造で新しいノードが生成される。 このdefaultは以下のようである。

Server.default.boot;
a = Synth.new(\default); // creates a synth in the default group of the default Server
a.group; // Returns a Group object. Note the ID of 1 (the default group) in the post window
グループのもう一つの大きな効用は、「set」メッセージを一斉にいくつものグループに送ることである。 以下のサンプルでは、1行目を送ってグループを定義し、次の行で同時に鳴る4音が起動される。 こんな音である。 そして3行目をenterすると全ての音量がとても小さくなり。4行目でストップする。

g = Group.new;

4.do({ { arg amp = 0.1; Pan2.ar(SinOsc.ar(440 + 110.rand, 0, amp), 1.0.rand2) }.play(g); });

g.set(\amp, 0.005); // turn them all down

g.free;
この後には、以下のようなGroupのオブジェクト指向的なインヘリタンス(継承)などの話題があったが、軽くスルーした。

Group.superclass; // this will return 'Node'

Group.superclass.openHelpFile;

Group.findRespondingMethodFor('set'); // Node-set

Group.findRespondingMethodFor('postln'); // Object-postln;

Group.helpFileForMethod('postln'); // opens class Object help file
これで「Group」はおしまいで、その次は、まだまだ地味に続くが、 Getting StartedBuffers である。 サウンドを単に生成したり、サウンド入力にリアルタイムのエフェクトを加えるだけでなく、サウンドファイルを読み込んで再生したり、ライブでサンプリングするためには、まぁバッファは当然、必要だろう。 バッファ・オブジェクトを生成してメモリを割り当てるのは、以下のようにする。

s.boot;

b = Buffer.alloc(s, 100, 2); // allocate 2 channels, and 100 frames 

b.free; // free the memory (when you're finished using it)
たぶんサンプリングのフレームは44.1KHzとかだろう。 「秒」で定義するには、以下のようにすればよい。

b = Buffer.alloc(s, s.sampleRate * 8.0, 2); // an 8 second stereo buffer
Bufferはallocメソッドの他に「read」クラスメソッドを持ち、これでサウンドファイルを読み込み、UGen PlayBufを用いて以下のように再生できる、という。 ただしこれでは、サウンドファイルへのパス指定が謎である(^_^;)。実行させると以下のようになった。

// read a soundfile
b = Buffer.read(s, "sounds/a11wlk01.wav");

// now play it
(
	x = SynthDef(
		"tutorial-PlayBuf",
		{
			arg out = 0, bufnum;
			Out.ar(
				out,
				PlayBuf.ar(
					1, bufnum, BufRateScale.kr(bufnum)
				)
			)
		}
	).play(s,[\bufnum, b]);
)

x.free; b.free;
「ファイルがありません」のようなメッセージが出るかと思ったら、 こんな音が鳴った。 つまりこれは、「sounds/」という指定でdefaultで探しに行くのだろう。 探してみると以下のように、アプリのSuperColliderの直下にあった。

考えてみると、SuperColliderではソースは全て完全にテキストファイルで、これをpostウインドウにコピペしては選択(ハイライト)してenterしているので、Processingなどのようにプロジェクトをどこかに保存・・・というのがあまり無いのだった。 しかし、アプリのフォルダの直下というのは一般性が無いので、何か、明示的に指定する方法があるのだろうか。 とりあえずテキストでは、「PlayBuf」を以下のように解説している。


PlayBuf.ar(
	1, // number of channels
	bufnum, // number of buffer to play
	BufRateScale.kr(bufnum) // rate of playback

	)
サーチパスの解説が無いまま、次には以下のように、ストリーミングにより、大きなサウンドファイルを鳴らすサンプルである。 こんな音が鳴った。 PlayBufと違って、ファイルの再生速度を指定する自由度が無いが、こちらの方が使用メモリを削減できる。

(
	SynthDef(
		"tutorial-Buffer-cue",
		{
			arg out=0,bufnum;
			Out.ar(
				out,
				DiskIn.ar( 1, bufnum )
			)
		}
	).send(s);
)

b = Buffer.cueSoundFile(s,"sounds/a11wlk01-44_1.mp3", 0, 1);
y = Synth.new("tutorial-Buffer-cue", [\bufnum,b], s);

b.free; y.free;
いろいろなインスタンスをpostウインドウで調べてみろ、というのでやってみると以下のようになった。

// watch the post window
b = Buffer.read(s, "sounds/a11wlk01.wav");
Buffer(0, nil, nil, nil, sounds/a11wlk01.wav)

b.bufnum;
0

b.numFrames;
188893

b.numChannels;
1

b.sampleRate;
44100

b.free;
Buffer(0, 188893, 1, 44100, sounds/a11wlk01.wav)
以下のサンプルは、「action」関数を使って同様なことを行ったものである。

(
	b = Buffer.read(
		s, "sounds/a11wlk01.wav", action:
		{
			arg buffer; 
			x = {
				PlayBuf.ar(
					1, buffer, BufRateScale.kr(buffer)
				) 
			}.play;
		}
	);
)

x.free; b.free;
そしていよいよ、「PlayBuf」とペアになる、「RecordBuf」である。 以下の例では、まず「b」にrecordバッファを定義し、次に「x」のSynthDefでピンクノイズを生成して再生するとともに「b」にレコーディングし、その後で「x」を解放した後に、「b」のSynthDefでバッファを再生している。

b = Buffer.alloc(s, s.sampleRate * 5, 1); // a 5 second 1 channel Buffer

// record for four seconds
(
	x = SynthDef(
		"tutorial-RecordBuf",
		{
			arg out=0,bufnum=0;
			var noise;
			noise = PinkNoise.ar(0.3); // record some PinkNoise
			RecordBuf.ar(noise, bufnum); // by default this loops
		}
	).play(
		s,[\out, 0, \bufnum, b]
	);
)

// free the record synth after a few seconds
x.free;

// play it back
(
	SynthDef(
		"tutorial-playback",
		{
			arg out=0,bufnum=0;
			var playbuf;
			playbuf = PlayBuf.ar(1,bufnum);
			FreeSelfWhenDone.kr(playbuf); 
			// frees the synth when the PlayBuf 
			//has played through once
			Out.ar(out, playbuf);
		}
	).play(
		s,[\out, 0, \bufnum, b]
	);
)

b.free;
バッファというのはデータのカタマリなので、以下の例のように「set」と「get」で、それぞれにアクセスできる。

b = Buffer.alloc(s, 8, 1);
Buffer(1, 8, 1, 44100, nil)

b.set(7, 0.5); // set the value at 7 to 0.5
Buffer(1, 8, 1, 44100, nil)

b.get(7, {|msg| msg.postln}); // get the value at 7 and post it when the reply is received
Buffer(1, 8, 1, 44100, nil)
0.5

b.free;
Buffer(1, 8, 1, 44100, nil)
また、以下の例のように「setn」と「getn」で、バリューのレンジを変更/参照できる。

b = Buffer.alloc(s,16);
Buffer(1, 16, 1, 44100, nil)

b.setn(0, [1, 2, 3]); // set the first 3 values
Buffer(1, 16, 1, 44100, nil)

b.getn(0, 3, {|msg| msg.postln}); // get them
Buffer(1, 16, 1, 44100, nil)
[ 1, 2, 3 ]

b.setn(0, Array.fill(b.numFrames, {1.0.rand})); // fill the buffer with random values
Buffer(1, 16, 1, 44100, nil)

b.getn(0, b.numFrames, {|msg| msg.postln}); // get them
Buffer(1, 16, 1, 44100, nil)
[ 0.19187498092651, 0.70660436153412, 0.42570316791534, 0.7228307723999, 
0.18033063411713, 0.29107618331909, 0.19827687740326, 0.19510293006897, 
0.8539137840271, 0.18318498134613, 0.47304499149323, 0.28701937198639, 
0.10247886180878, 0.90541768074036, 0.96749472618103, 0.0063588619232178 ]

b.free;
Buffer(1, 16, 1, 44100, nil)
同時にset_getできるデータ数には上限(defaultでは、UDPの制限に起因する上限として1633個)がある。 これを越えたい場合には、以下の例のように、「loadCollection」と「loadToFloatArray」という2種類の方法がある。 「FloatArray」は、「Array」のサブクラスで、floatデータを持っている。

(
	// make some white noise
	v = FloatArray.fill(44100, {1.0.rand2});
	b = Buffer.alloc(s, 44100);
)
Buffer(1, 44100, 1, 44100, nil)

(
	// load the FloatArray into b, then play it
	b.loadCollection(
		v, action: 
		{
			|buf| 
			x = {
				PlayBuf.ar(
					buf.numChannels, buf, BufRateScale.kr(buf), loop: 1
				) 
				* 0.2 
			}.play;
		}
	);
)
Buffer(1, 44100, 1, 44100, /tmp/-1915485328)

x.free;
Synth("temp__13" : 1007)

// now get the FloatArray back, and compare it to v; this posts 'true'
// the args 0, -1 mean start from the beginning and load the whole buffer
b.loadToFloatArray(0, -1, {|floatArray| (floatArray == v).postln });
Buffer(1, 44100, 1, 44100, nil)
true

b.free;
Buffer(1, 44100, 1, 44100, nil)
最後に「Other Uses For Buffers」として、ウェーブシェープのような多量のデータを格納して、 これを波形として参照するサンプルが以下である。 こんな音が鳴った。

b = Buffer.alloc(s, 512, 1);

b.cheby([1,0,1,1,0,1]);
(
	x = play(
		{ 
			Shaper.ar(
				b, 
				SinOsc.ar(
					300, 0, Line.kr(0,1,6)
				),
				0.5
			)
		}
	);
)

x.free; b.free;

2011年3月3日(木)

午後のオフ会のため有休を取った昨日は午前中だけだったので、 Processing日記 を進めただけでSuperColliderはお休みだった。 そして今日は、午前には「年間でもっとも悩ましい学科会議」などがあり、さらにサーバ引っ越しの関係とか、 iPad2の発表とか、 カンニング事件のニュースなどが盛り上がり、SuperColliderの続きを進める時間は午後にちょっとだけ確保、となった。 3/6(日)は停電工事のためお休み、7日から10日は沖縄行き(*^o^*)、11日は終日の学科会議、 などの予定があるので、ある程度まで進めるのは今週だけなのに(^_^;)。 Getting Started も残り3項目となり、今日は Scheduling Events からである。

音楽は時間とともに起きるものであり、物事の生起を制御するのは本質的に重要である。 SuperColliderでは「clocks」に基づいて全てをスケジューリングする。 SuperColliderの「clock」には2つの主要な機能がある。「今が何時か」と「それが起きるのは何時か」である。 音楽のシーケンスに使うのは「TempoClock」である。 SuperColliderにはこの他に2種の「clock」がある。 「SystemClock」は、音楽に固有の厳格なタイミングのために使う。 「AppClock」は、システムに余裕がある時に適度に仕事を行うpriorityの低いクロックであり、グラフィクスの更新など、あまり重要でない処理に使われる。 このあたりはMax/MSP/jitterとまったく同じである。

さて「Scheduling」である。Schedulingとは、未来のある時刻に何かを行う、とclockに指示することである。 最初のサンプルとして


SystemClock.sched(5, { "hello".postln });
を実行させると、直後には

となり、5秒後には

となった。 これは、上の例で判るように、通常は命令の実行とともに「即刻」実行され、「SystemClock」の返り値はその時のSystemClockである。 ここで「sched」(スケジュールのデーモン?)は、「相対的なスケジューリング」を行う。 そこで、「SystemClock.sched(5, { ・・・ })」とすると、5秒後に「{ ・・・ }」の処理が実行され、返り値はその時のSystemClockである。 以下の例では、「絶対時間でのスケジューリング」を行う「schedAbs」を使っている。 ここでは「SystemClock」でなく「TempoClock」を使っているが、これは色々な「TempoClock」をテンポを変えながら同時に使う、という意味では、より音楽/サウンドの処理に向いている。


(
	var timeNow = TempoClock.default.beats;
	"Time is now: ".post; timeNow.postln;
	"Scheduling for: ".post; (timeNow + 5).postln;
	TempoClock.default.schedAbs(
		timeNow + 5,
		{
			"Time is later: ".post;
			thisThread.clock.beats.postln;
			nil
		}
	);
)

「TempoClock」の初期値は「TempoClock.default」で参照するが、SuperCollider的には 「t = TempoClock.default」というように変数に割り当てるのが良い。 以下の例では。BPMを使って、同じことを実現している。 シーケンシングにはこれが向いている。


(
	var timeNow;
	TempoClock.default.tempo = 2; // 2 beats/sec, or 120 BPM
	timeNow = TempoClock.default.beats;
	"Time is now: ".post; timeNow.postln;
	"Scheduling for: ".post; (timeNow + 5).postln;
	TempoClock.default.schedAbs(
		timeNow + 5,
		{
			"Time is later: ".post;
			thisThread.clock.beats.postln;
			nil
		}
	);
)

順にenterした結果を並べた以下の例のように、「thisThread.clock」で現在時刻を問い合わせればよい。 一度これがあれば後は「beats」で知ることができる。


SystemClock.beats;
1412.895814268

TempoClock.default.beats;
1715.576808458

AppClock.beats;
1416.501044419

thisThread.clock.beats;
1418.236951041

ここで次のトピックとして「What can you schedule?」と書かれていた。 サンプルとして1行プログラムがあり、実行してみろ、とある。 実行してみると、以下のように何も起こらなかった。(^_^;)


TempoClock.default.sched(5, "hello");

これは当然である。 「TempoClock.default.sched(5, "hello")」の第2引数の「"hello"」はデータである。 スケジューリングとしては、ここに何らかのアクション、つまり

が置かれなければならないのだ。「Function」は既に登場しているし、「Routine」と「Task」は後に登場する。 もちろん、他にもいくつかある。 そして「注意」として、以下の例が示されている。 このプログラムは1秒ごとにランダムな数を表示し、無限に続いてしまう。

// fires many times (but looks like it should fire just once)
TempoClock.default.sched(1, { rrand(1, 3).postln; });

これを1回だけ実行して停止させるためには、引数「nil」を以下のように加えればよい。 1回だけの実行は、デバッグなどで活用する。


// fires once
TempoClock.default.sched(1, { rrand(1, 3).postln; nil });

これで「Scheduling Events」はおしまい、次は Getting StartedSequencing with Routines and Tasks である。 これは上の項目で出て来た、残り2つのアクションである。

Routine」とは、データ処理のアクションである。 以下の実行例でこれは簡単に判る。


r = Routine(
	{
		"abcde".yield;
		"fghij".yield;
		"klmno".yield;
		"pqrst".yield;
		"uvwxy".yield;
		"z{|}~".yield;
	}
);
a Routine

r.next; // get the next value from the Routine
6.do({ r.next.postln });
abcde
fghij
klmno
pqrst
uvwxy
z{|}~
6
この例では、格納した文字列を呼び出したが、「routine」ではデータを生成する、という重要な機能もある。 また、「clock」で「routine」をスケジューリングすると、時間データが返り値となる。 これは音楽のシーケンシングで使える。 以下のように、clockで無限ループとなったように、routineでも無限ループが出来る。

r = Routine(
	{
		var delta;
		loop
		{
			delta = rrand(1, 3) * 0.5;
			"Will wait ".post; delta.postln;
			delta.yield;
		}
	}
);

r.next;

TempoClock.default.sched(0, r);

r.stop;
いよいよここで、表示(postln)をplayに替えて、スケジューリングによってループのフレーズを生成させてみよう。 以下のような例で、こんな音が鳴った。 これが、我々の初めてのSuperColliderシーケンス、というわけである。

(
	SynthDef(
		\singrain,
		{
			|freq = 440, amp = 0.2, sustain = 1|
			var sig;
			sig = SinOsc.ar(freq, 0, amp) * EnvGen.kr(
				Env.perc(0.01, sustain), doneAction: 2
			);
			Out.ar(0, sig ! 2); // sig ! 2 is the same as [sig, sig]
		}
	).send(s);

	r = Routine(
		{
			var delta;
			loop
			{
				delta = rrand(1, 3) * 0.5;
				Synth(
					\singrain,
					[
						freq: exprand(200, 800),
						amp: rrand(0.1, 0.5),
						sustain: delta * 0.8
					]
				);
				delta.yield;
			}
		}
	);
)

r.play;

r.stop;

「routine」では、音楽の用途には限界がある。 いったん「routine」を止めた場合には、もう一度、最初から呼び出さないといけない。 音楽では「ポーズ」と「再スタート」が一般的であり、このためにあるのが「task」である。 以下の例では、「t = Task({・・・」として無限ループのダイアトニックスケールをピッチにmidiを使って発生し、 「t.stop;」でいったんポーズで停止し、「t.play;」でその続きから再開している。 こんな音になった。


(
	t = Task(
		{
			loop
			{
				[60, 62, 64, 65, 67, 69, 71, 72].do
				(
					{
						|midi|
						Synth(
							\singrain, 
							[
								freq: midi.midicps,
								amp: 0.2,
								sustain: 0.1
							]
						);
						0.125.wait;
					}
				);
			}
		}
	).play;
)

// probably stops in the middle of the scale
t.stop;

t.play; // should pick up with the next note

t.stop;

次のトピックは「When do you want to start?」である。 音楽の要素をいろいろにSuperColliderで記述したとして、それぞれのスタートこそが、実際の音楽を構成していくので、ここは重要だ。 「play」は、呼ばれたらそこで即刻、実行されるが、それだけでは音楽とはならないからである。 「play」には、以下のような引数がある。


aRoutine.play(clock, quant)

aTask.play(argClock, doReset, quant)
それぞれの定義は以下である。 意味は書かれている通りである。 「quant」とはクオンタイズの事で、西洋音楽の「○小節目の○拍目」という2つの数値をとる。 以下の例では、まず「f」としてダイアトニックスケールをピッチにmidiを使って発生させる。

f = {
	Task(
		{
			loop
			{
				[60, 62, 64, 65, 67, 69, 71, 72].do
				(
					{
						|midi|
						Synth(
							\singrain,
							[
								freq: midi.midicps,
								amp: 0.2,
								sustain: 0.1
							]
						);
						0.25.wait;
					}
				);
			}
		}
	);
};
ここで、

t = f.value.play(quant: 4); // start on next 4-beat boundary
を実行すると、まず こんな音がする。 そこに、さらに

u = f.value.play(quant: [4, 0.5]); // next 4-beat boundary + a half-beat
を実行すると、 こんな音になる。 これは8分音符で上行しているスケールに対して、4分音符の分だけ遅れてスタートしているので、 3度音程でハモっている事になる。 これを、後で合流するものを

u = f.value.play(quant: [4, 0.75]); // next 4-beat boundary + a half-beat
を実行すると、 こんな音になった。 これは8分音符で上行しているスケールに対して、8分音符で3個分だけ遅れてスタートしているので、 4度音程でハモっている事になる。

このようなシステムを使うと、いわゆるノートシーケンスをデータroutineで実現できることになる。 以下のサンプルを実行すると、 こんな音がした。 自分としては好きではないが、まさに「打ち込み」音楽である。(^_^;)


(
	var midi, dur;
	midi = Routine(
		{
			[60, 72, 71, 67, 69, 71, 72, 60, 69, 67].do(
				{
					|midi| midi.yield
				}
			);
		}
	);

	dur = Routine(
		{
			[2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3].do(
				{
					|dur| dur.yield
				}
			);
		}
	);

	SynthDef(
		\smooth, 
			{
				|
					freq = 440,
					sustain = 1,
					amp = 0.5
				|
				var sig;
				sig = SinOsc.ar(freq, 0, amp)
				 * EnvGen.kr(
					Env.linen(0.05, sustain, 0.1), doneAction: 2
				);
				Out.ar(0, sig ! 2)
			}
	).send(s);

	r = Task(
		{
			var delta;
			while 
			{
				delta = dur.next;
				delta.notNil
			} {
				Synth(
					\smooth,
					[
						freq: midi.next.midicps,
						sustain: delta
					]
				);
				delta.yield;
			}
		}
	).play(
		quant: TempoClock.default.beats + 1.0
	);
)
「routine」がデータのために、「task」がplayのために使われている。 ここでは「while」が使われて、シーケンスデータの最後になるまでは無限ループ、としている。 シーケンスデータの長さは可変長なので、「while」が定番となりそうである。

このセクションの最後は「A note on server messaging and timing」である。 SuperColliderでは、内部的にサーバへの転送にOSCを使っているらしい。 OSCについては ここ にも書かれている。 Synthでサウンドを生成していると、このOSCのネットワーク遅延が発生するために、ある不定の遅れ(latency)が存在する。 そこで、システムのclockに対して、サーバの遅れ「ServerTiming」まで考慮する必要がある。 以下の例は、「SynthDef」を毎秒10回、呼び出しているので、システムによっては(遅いパソコンなどの場合)、サウンドの間隔にばらつきが出るのだ、という。


(
	t = Task(
		{
			loop
			{
				Synth(
					\singrain,
					[
						freq: exprand(400, 1200),
						sustain: 0.08
					]
				);
				0.1.wait;
			}
		}
	).play;
)

t.stop;
しかしこのサウンドは こんな音で、ばらつきは感じられない(^_^;)。 アクティビティモニタで見てみると、以下のようにCPUはほとんど暇だから仕方ないのか。

そこで、いじわるテストとして、以下のようにQuickTime ProでMP4をM4Vに変換する、というタスクを走らせて、2つのCPUをほぼ100%にして、同じサウンドを鳴らしてみた。 しかしそれでも、 こんな音で、ばらつきは感じられない(^_^;)。

mac miniとはいえ、以下のようなスペックではちょっと差が出ないのかもしれない。

このレイテンシを気にする場合には、 以下の「makeBundle」メソッドを使うとよい、とのことだが、鳴らしたところではまったく変化が無かった。


(
	t = Task(
		{
		loop	
			{
				s.makeBundle(
					s.latency,
					{
						Synth(
							\singrain, 
							[
								freq: exprand(400, 1200),
								sustain: 0.08
							]
						);
					}
				);
				0.1.wait;
			}
		}
	).play;
)

t.stop;
あと1項目を残して、今日はここまでである。

2011年3月5日(土)

昨日は Processing日記 を進めたが、今日は午前に沖縄ミーティングがあり、午後にはゼミのちひろちゃんがシステム返却のついでにiMacセットアップを手伝ってくれるので、またまた時間がほとんど取れない。 明日3/6は停電工事のSUACネット停止のためお休み、そして3/7-10は沖縄、3/11は終日学科会議、3/12は一般後期入試、と予定満杯で、次は卒業式の週からのスタートとなりそうだ。 Getting Started も残り1項目となったので、区切りのためにも、最後の Sequencing with Patterns だけは見ておくことにしよう。

ここではシーケンシングを「パターン」を用いて、データストリームとして簡単に扱う。 以下の例では「Over the Rainbow」のメロディー音列が生成されている。


r = Routine(
	{
		[60, 72, 71, 67, 69, 71, 72, 60, 69, 67].do(
			{
				|midi| midi.yield
			}
		);
	}
);
while {
	(m = r.next).notNil
} {
	m.postln 
};

60
72
71
67
69
71
72
60
69
67
nil
「Pseq (Pattern-sequence)」を使うと、同じように、以下のようになる。

p = Pseq([60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1);
r = p.asStream;
while {
	(m = r.next).notNil
} {
	m.postln
};

60
72
71
67
69
71
72
60
69
67
nil
これを用いると、「Over the Rainbow」のメロディーを生成するSuperColliderプログラムは以下のようになる。 こんな音がする。

(
	var midi, dur;
	midi = Pseq(
		[60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1
	).asStream;
	dur = Pseq(
		[2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3], 1
	).asStream;

	SynthDef(
		\smooth, 
			{
				|freq = 440, 
				sustain = 1, amp = 0.5|
				var sig;
				sig = SinOsc.ar(freq, 0, amp) 
				* EnvGen.kr(
					Env.linen(0.05, sustain, 0.1), doneAction: 2
				);
				Out.ar(0, sig ! 2)
			}
	).send(s);

	r = Task(
		{
			var delta;
			while {
				delta = dur.next;
				delta.notNil
			} {
				Synth(
					\smooth,
					[
						freq: midi.next.midicps,
						sustain: delta
					]
				);
				delta.yield;
			}
		}
	).play(
		quant: TempoClock.default.beats + 1.0
	);
)

「パターン」は他に何が出来るのか、については、以下のように色々と定義されているらしい。 ここらは、音楽の歴史からくるヒューリスティクスの塊である(^_^;)。

以下の例は、patternをネスティングさせて、次第に拡大する幅でのりランダムを生成させたものである。

p = Pwhite(
	0.0, Pseries(0.01, 0.01, inf), 100).asStream;
	// .all pulls from the stream until it returns nil
	// obviously you don't want to do this for an 'inf' length stream!
	p.all.plot;
)
t.stop;

さらにpatternでは、以下の例のように、パターンをランダムに入れ替えて、これを呼び出すたびにさらにシャッフルする事もできる。


p = Pn(Pshuf([1, 2, 3, 4, 5], 1), inf).asStream;
p.nextN(15); // get 15 values from the pattern's stream

[ 4, 5, 3, 2, 1, 2, 5, 1, 3, 4, 3, 4, 1, 2, 5 ]
より簡単にシーケンシングを実現するために「Pbind」がある。 これを使って「Over the Rainbow」のメロディーを生成するSuperColliderプログラムは以下のようになる。 ただしオリジナルテキストのサンプルにはいくつか教育的なバグがあり、エラーが出るので注意(^_^;)。 こんな音がする。

(
	SynthDef(
		\smooth,
		{
			|freq = 440, sustain = 1, amp = 0.5|
			var sig;
			sig = SinOsc.ar(freq, 0, amp) 
			* EnvGen.kr(
				Env.linen(0.05, sustain, 0.1), doneAction: 2
			);
			Out.ar(0, sig ! 2);
		}
	).add;

	p = Pbind(
		// the name of the SynthDef to use for each note
		\instrument, \smooth,
		// MIDI note numbers -- converted automatically to Hz
		\midinote, Pseq(
			[60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1
		),
		// rhythmic values
		\dur, Pseq([2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3], 1);
	).play;
)

詳しくは「streams」のドキュメントを参照する必要があるが、ここではおよそ、以下のような処理を実現している。

最後に以下のような例があって、最初のチュートリアルは終わりとなる。 これを「楽譜」として読めるのは・・・いつの事だろう(^_^;)。 まず、前半部分として以下のプログラムを走らせると、 こんな音がした。

(
	SynthDef(
		\bass, 
		{
			|freq = 440,
			gate = 1,
			amp = 0.5,
			slideTime = 0.17,
			ffreq = 1100,
			width = 0.15,
			detune = 1.005,
			preamp = 4|
			var sig,
			env = Env.adsr(0.01, 0.3, 0.4, 0.1);
			freq = Lag.kr(freq, slideTime);
			sig = Mix(
				VarSaw.ar(
					[freq, freq * detune], 
					0, width, preamp
				)
			).distort * amp
			* EnvGen.kr(
				env, gate, doneAction: 2
			);
			sig = LPF.ar(sig, ffreq);
			Out.ar(0, sig ! 2)
		}
	).add;

	TempoClock.default.tempo = 132/60;

	p = Pxrand(
		[
		Pbind(
			\instrument, \bass,
			\midinote, 36,
			\dur, Pseq([0.75, 0.25, 0.25, 0.25, 0.5], 1),
			\legato, Pseq([0.9, 0.3, 0.3, 0.3, 0.3], 1),
			\amp, 0.5, \detune, 1.005
		),
		Pmono(\bass,
			\midinote, Pseq([36, 48, 36], 1),
			\dur, Pseq([0.25, 0.25, 0.5], 1),
			\amp, 0.5, \detune, 1.005
		),
		Pmono(\bass,
			\midinote, Pseq([36, 42, 41, 33], 1),
			\dur, Pseq([0.25, 0.25, 0.25, 0.75], 1),
			\amp, 0.5, \detune, 1.005
		),
		Pmono(\bass,
			\midinote, Pseq([36, 39, 36, 42], 1),
			\dur, Pseq([0.25, 0.5, 0.25, 0.5], 1),
			\amp, 0.5, \detune, 1.005
		)
		], inf
	).play(quant: 1);
)
そして、後半部分として以下のプログラムをさらに走らせると、 こんな音になった。

// totally cheesy, but who could resist?
(
	SynthDef(
		\kik, 
		{
			|preamp = 1, amp = 1|
			var freq = EnvGen.kr(
				Env([400, 66], [0.08], -3)
			),
			sig = SinOsc.ar(
				freq, 0.5pi, preamp
			).distort * amp
			* EnvGen.kr(
				Env(
					[0, 1, 0.8, 0],
					[0.01, 0.1, 0.2]),
					doneAction: 2
			);
			Out.ar(0, sig ! 2);
		}
	).add;

	// before you play:
	// what do you anticipate '\delta, 1' will do?
	k = Pbind(
		\instrument, 
		\kik, 
		\delta, 1, 
		\preamp, 4.5, 
		\amp, 
		0.32
	).play(quant: 1);
)
それぞれを終了させるのは、以下である。

p.stop;
k.stop;
これでようやく、 Getting Started が終わりとなった。 ここまでを「日記(1)」として区切りにして、次は「日記(2)」として Workshop materials for Computer Music (G6002) から始めてみたいと思う。

SuperCollider日記(2)

「日記」シリーズ の記録