yoppa.org


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

つづく(たぶん…)

cmsf — 15 July 2014 11:38
勉強になります。