Blog
SuperCollider自主練 – 基本編
ここしばらく、openFrameworksやProcessingを主につかっているうちに、音から遠ざかってしまってる。もともとは、コンピュータ音楽が専門なのに…。ということで、この夏もういちど音と向きあおうと思いたち、SuperColliderに再入門してみようかと思い立つ。教材は、MIT Pressから刊行された”The SuperCollider Book“。この本を読みながら、SCの基本をもういちどおさらいしてみたい。
まずは、最初の章から順番に。
Chapter 1: Beginner’s Tutorial
1.1 Hello World
まずは、基本から。
初心者向けチュートリアルといいながら、いきなり冒頭でオシレータ、LFO、エンベロープなどの音響合成の基礎知識を前提としていると宣言される。この突き放し感がSCらしい感じ。1章のねらいは言語の成り立ちを詳細に学ぶというよりは、実際に音のでるサンプルを通してSCで出来ることを体験していこうという主眼のよう。
まずは、Hello Worldと、サーバーのブート:
"Hello world"
Server.default = s = Server.internal.boot;
そして、実際に音の出るサンプル。シンプルだけど出てくる音はなかなか面白い。
play({SinOsc.ar(LFNoise0.kr(12, mul: 600, add: 1000), 0.3)})
ここで簡単にプログラムの実行方法や停止のやりかた、ボリュームの上げ下げなどの基本操作の解説と、エラーメッセージの読み方など。さらにもう一つ音の出るサンプル。
play({RLPF.ar(Dust.ar([12, 15]), LFNoise1.ar(1/[3, 4], 1500, 1600), 0.02)})
ここで、Dust.arの仲の12と15や、LFNoise1の3と4の数値をいろいろ変化させてみろ、との指示。こうやって実際に数値を変えてすぐに実践できるのがSCの良さの一つかもしれない。
ここまでで、オーディオシグナル”.ar”の意味や関数”{…}“の意味などの説明は一切ないが、まあまずは体験しながらということなのだろう。
次にいままでは1行で書いていたプログラムを機能ごとに改行してインデントした、加算合成のサンプル。こうやって書くとわかりやすい。
play({
var sines = 10, speed = 6;
Mix.fill(sines,
{arg x;
Pan2.ar(
SinOsc.ar(x+1*100,
mul: max(0,
LFNoise1.kr(speed) +
Line.kr(1, -1, 30)
)
), rand2(1.0))})/sines})
このサンプルでは、新たに関数の中での変数(var)の使用や、引数(arg)なども出てきているが、ここでもあまり具体的な解説はなし。「この記述がギリシャ語のように感じるかもしれないが、おいおい学んでいきましょう」という感じ。
1.2 メッセージと引数
メッセージと引数という概念の紹介。
メッセージとは、複数のコンマで区切られたパラメータのリストをもった小文字で始まる命令。
message(arg1, arg2, arg3…)
メッセージは、大文字で始まる言葉(つまりクラスのことか?)の後に接続されることもある。
SinOsc.ar(arglist)
Mix.fill(arglist)
他のOOP言語に慣れていると、逆にオブジェクトに対するメッセージだけでなくメッセージ単体で使用できることの方が奇異に感じるが、ここらへんはSmallTalkから来ているものなのだろうか?
メッセージの例としてランダムな値の生成について。単純な乱数と指数範囲での乱数exprandの紹介など。引数にfloat()小数点以下の数)を入れると出力もfloatになり、整数(int)だと出力結果もintとなるなど。
rand(100)
exprand(1.0, 100.0)
例えば、10から100のランダムの数値を使用する場合でも、音程をランダムに生成する場合にはexprandomを使うべきという話。なぜなら我々の耳は音程を指数的に知覚しているから。こうした乱数の説明も、音を基本にしているところがSC的。
ランダムシードを指定して、常に同じランダム数列を得る方法もある。これは知らなかった…
thisThread.randSeed = 666; {rand(10.0)} ! 10;
そのあとは様々なメッセージの紹介。くりかえし、小数点の桁の切り捨て、ソートなど。
dup("echo", 20)
round([3.141, 5.9265, 358.98], 0.01)
sort([23, 54, 678, 1, 21, 91, 34, 78])
round(dup({exprand(1, 10)}, 100), 0.1)
sort(round(dup({exprand(1, 10)}, 100), 0.1))
こういったメッセージを必要に応じてすぐに使用できるようになると、気の効いたプログラムがつくれそう。
1.3 ネスティング(Nesting)
ネスティングについて。SCではあるメッセージの出力結果を、そのまま別のメッセージの引数として使用できる。それをさらに別のメッセージの引数にしていってというように、どんどんネストしていけるという話。
実例がわかり易い。
exprand(1.0, 1000.0)
dup({exprand(1.0, 1000.0)}, 100)
sort(dup({exprand(1.0, 1000.0)}, 100))
round(sort(dup({exprand(1.0, 1000.0)}, 100)), 0.01)
こうしたネストの使用を音響合成の例でみてみる。
play(
{
CombN.ar(
SinOsc.ar(
midicps(
LFNoise1.ar(3, 24,
LFSaw.ar([5, 5.123], 0, 3, 80)
)
),
0, 0.4),
1, 0.3, 2)
}
)
様々なユニットジェネレータがネストされている。
1.4 レシーバー.メッセージ、コメント (Receiver.message, Comments)
1.3の例では、大文字で始まる命令にメッセージがドット(.)で接続されていた。SinOsc.ar、LFNoise1.ar など。これらはユニットジェネレータ(UGens)と呼ばれ、デジタルオーディオに特化した数値のストリームを出力する。これらはより広範な定義でいうと「レシーバー(Receiver)」と呼ばれるもの。レシーバーは、引数をもったメッセージからの指令によって動作する。
例えば、
LFNoise1.kr(10,100)
これは「-100から100の範囲でランダムな値を生成するタイプ1の低周波のノイズ生成器でノイズを1秒に10個生成する」という意味になる。
数(Numbers)、関数(functions)、配列(arrays)、文字列(strings)もメッセージをドットで継いで何をするか伝えることができる。
[45, 13, 10, 498, 78].sort
"echo".dup(20)
50.midicps
444.cpsmidi
100.rand
{100.rand}.dup(50)
[1.001, 45.827, 187.18].round(0.1)
"I've just picked up a fault in the AE35 unit".speak
などなど。
ところで、ちょっと前のサンプルで出てきた、rand(100) と 100.rand はどう違うのか? 結論からいうと両者は同じ。rand(100) は関数的な表記法。100.rand はメッセージによる表記になる。どちらの記述方法を使うかはケースバイケース。
メッセージを利用した書式は、ドットで継いでいくことでネストを実現できる。
1000.0
1000.0.rand
1000.0.rand.round(0.01)
1000.0.rand.round(0.01).post
{1000.0.rand.round(0.01).postln}.dup(100).plot
{1000.0.rand.round(0.01).postln}.dup(100).postln.sort.plot
1000.0.rand.round(0.01).postln.asString.speak
つづく(たぶん…)