SuperCollider日記(1)
長嶋 洋一
(一部 Processing日記かも)
Part 2 Part 3 Part 4 Part 5 Part 6 Part 7
2011年2月24日(木)
マルチメディア室のコンピュータが新しくなる節目の2011年2月23日、 こんな作業 をやっていて、あと42台も同じ作業があるのか・・・とウンザリしたが、ちゃんとその中に SuperCollider と Processing を入れていたのを思い出し、フト、翌日の2011年2月24日、思い立った。 そういえば、これらのソフトはほとんど手つかずだったのだ(^_^;)。SuperCollider は、まだMacOS7.5とか8.6の時代に、 (旧)SuperCollider を使ってパフォーマンスするところまでやっていたが、その後は Kyma を使うこともなくなるとともに、もっぱら Max/MSP/jitter だけになってしまっていた。
そこで今回、新学期には、最近レベルが上がってきた学生に、 Max/MSP/jitter だけでなく、 SuperCollider と Processing も紹介して、ごく一部の学生であっても、これらを活用できるようにしよう、と決意した。 明日は一般前期入試だし、いろいろあって時間は限られているが、せっかくなので、 春休みの期間を活用して、両方を同時に並行して勉強してみよう。
昼過ぎまで教授会や委員会があったが、翌日が入試ということで午後は入構制限となって静かになったので、 まずは環境設定から開始した。 まずは 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
- グラフィクスはProcessing
- プログラミングはMax5
さて、せっかくなので、 チュートリアル のページに行ってみた。 さすがに歴史のあるSuperColliderである、「Recommended introductory tutorials」 というだけで、たくさんあった。 これ は、ダウンロードしたパッケージに入っているもので、さらに続編として これ もあるという。
また、 これ は、音響合成・音響処理に関するワークショップの全コースをSuperColliderで行ったもののテキストのようで、これでほぼSuperColliderで出来ることを概観できそうな力作である。
もう1件の これ はNot Foundになっていた。ユタ大学での講義資料だったのかもしれない。そこで、とりあえず、まずは これ に従ってみることにした。 最初はもちろん「Hello World」である。 とりあえずSuperColliderのアプリのアイコンをダブルクリックすると、昔のSuperColliderと同じようなテキストウインドウと、見慣れない2つのGUIウインドウが現れた。 そこにコピペで
と入れて、リターンキーを叩いたが何も起きなかった(^_^;)。 そう、SuperColliderではリターンキーとは別に、キーボードの端にある「enter」キーが特別の役割を担っているのだった。 そこで「enter」を叩くと、以下のように出た。これがSuperColliderの「Hello World」である。"Hello World!".postln; 「enter」キーに相当する機能は、「control+リターン」「shift+リターン」でもOKである。 また、複数行にわたるプログラムを実行させるには、例えば
という2行を実行させるためには、"Hello World!".postln; "Hello SC!".postln; のいずれでも良い。後の2つの場合、ソースの見た目は
- この2行を全てハイライトさせた状態で「enter」キー
- カッコ(中カッコでない普通のカッコ)でこの2行を囲って、末尾で「enter」キー
- カッコ(中カッコでない普通のカッコ)でこの2行を囲って、全てハイライトさせた状態で「enter」キー
とか( "Hello World!".postln; "Hello SC!".postln; ) とか("Hello World!".postln; "Hello SC!".postln;) になるが、いずれもOKである。 なお、後では中カッコ { } とかカギカッコ [ ] が主役になるので注意が必要である。( "Hello World!".postln; "Hello SC!".postln; ) 昔のSuperColliderであれば、単独のアプリだったので、ここでサウンドを記述する1行を入れて「enter」キーが出たが、SuperCollider3になってサーバシステムとなったので、まず準備が必要になった。 謎だった、以下のウインドウの出番である。
「localhost server」とは、ネットワークの向こうにある他のコンピュータに対する「自分」のことである。 SuperColliderでは、サウンドを鳴らすコンピュータが自分であるのとネットワークの向こうにあるのとを同等に扱える。 ここで「Boot」ボタンで起動させると、あれこれメッセージが表示されて、以下のようにボタンが緑色「Quit」になる。
このlocalhost serverは、テキストウインドウからのコマンドで
としてもs.boot; としても起動できる。終了はServer.local.boot; あるいはs.quit; である。 これでようやくサウンドが鳴る環境が出来た。localhost serverを走らせて、次に進もう。Server.local.quit; テキストは次のページに進んで これ になった。 この最初にある
というプログラムをコピペして「enter」キーにより、2ヘルツの唸りのある440Hzのサインが鳴り出し、ずっと続いた。 こんな音である。{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play; ただし、チュートルアルはこの後、関数 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 この次のページでは、
を鳴らしつつ、音のパラメータとして、ピッチと位相と音量を解説している。 Max/MSPではサウンドの位相はあまり考えていなかったが、SuperColliderではきちんと指定できるのが強力である。 上記は{ SinOsc.ar(440, 0, 0.2) }.play; とすることで意味を把握できる。ここまでは簡単なのでサッサと読み流した。 そしてこのセクションの最後は、( { // 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 ) ということで、振幅変調(AM)のかかったサインとなった。 こんな音である。 localhost serverパネルにあった「record」ボタンで、このように簡単に鳴っているサウンドがレコーディングできるので、色々に活用できそうである。( { var ampOsc; ampOsc = SinOsc.kr(0.5, 1.5pi, 0.5, 0.5); SinOsc.ar(440, 0, ampOsc); }.play; ) 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 である。
まずは、あらためて
からスタートである。 実行させると、440Hzと442Hzのサイン波の唸りが気持ちよく鳴っている。 ここでの解説では、「中カッコ { } は関数」そして「四角カッコ [ ] は(Objectsの)配列」である、と強調している。 「四角カッコ [ ] 」の中でコンマで区切られて列記されているのがオブジェクトである。 SuperColliderではあらゆるものがObjectであり、その集合もまたObjectである。 これが、シンプルでも複雑なサウンドを生み出すSuperColliderの肝の部分だという。{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play; 配列の定義と参照は、以下のようによくあるスタイルである。
これは多くの処理系で見かけるが、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); のようにサウンドをplayする局面では、この2つの要素がleftとrightのステレオサウンドチャンネルに対応するという。 もっと配列の要素を多くした場合には、簡単に多チャンネルオーディオとなるらしいが、出力デバイスが無いと検証できない。{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play; そして面白いのが、上記のプログラムは以下のようにも書けるという。
これはなかなかトリッキーだが、より簡潔に複雑な構造を記述できそうな気がする。{ SinOsc.ar([440, 442], 0, 0.2) }.play; 次に提示されたサンプル
を何度か実行してみると、「choose」は配列の要素からランダムに選ぶということで、刻々と違うサウンドが加わって鳴る。 そして、コンマで切った2つの数値の時には左右で違う音が鳴り(ステレオ)、1つの数値の時にはモノラルになる。 これで複雑なことが簡単に出来そうである。( { var freq; freq = [[660, 880], [440, 660], 1320, 880].choose; SinOsc.ar(freq, 0, 0.2); }.play; ) 先日はAM(振幅変調)をやっていたが、ここでもう1つ、パンニング「Pan2」が登場した。 たった1行の
を実行させたサウンドが こんな音である。 つまり、「Pan2.ar」の「.ar」はオーディオレート、すなわちサウンド領域であり、その第1引数はサウンドソースのようだ。 ここではピンクノイズである。 そして第2引数はパンニングのパラメータで、「SinOsc.kr(0.5)」とサインでゆっくり動く。 どうやら「.kr」は、詳細不明ながらサウンド領域よりも低速の変化パラメータを指定するらしい。{ Pan2.ar(PinkNoise.ar(0.2), SinOsc.kr(0.5)) }.play; これで「Presented in Living Stereo」は終わったので、その次の Mix it Up に進むことにした。 今度は同じチャンネルで複数のサウンドを足し合わせる、ということになる。 Max/MSPではサウンドのオブジェクトからの「黄色と黒の紐」をどんどん合流させればサウンドがmixされたが、SuperColliderでも簡単であった。 最初のサンプル
を実行させてみると、 こんな音である。 これはソースを見れば一目瞭然、ピンクノイズと440Hzサインとその完全5度上の鋸歯状波のミックスである。 それぞれの振幅を「0.2」と小さくすることで、最終的なサウンド出力が-1から1までの間に入って、 クリップしないようにしているわけである。 試しに{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.play; とすると、音量増加だけでなく割れたような音になった。 おそらく自動クリッピングは無いので(???)、ここは注意が必要だろう。{ PinkNoise.ar(0.5) + SinOsc.ar(440, 0, 0.5) + Saw.ar(660, 0.5) }.play; 次に登場するのは「Mix」という新しいクラスである。これは配列として指定されたチャンネルを1チャンネルにミックスする、あるいは複数の配列をチャンネルの配列にミックスするものである。 「.postln」で起動されたUnit Generatorの様子がpostウインドウに表示される、ということで、
を実行させてみると、確かにモノラルで こんな音である。 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; ) このプログラムでの、「Mix.new」という「.new」というのは、新しいオブジェクトを呼び出すクラスであるが、実は短縮形としては、「Mix」でも動く、ということであった。 実際に、両者それぞれとも、「.new」を付けても省略しても同じ動作だった。
省略しても同じだから不要、というわけではなくて、Mixには他に「Mix.fill」というクラスもある。 その例示として
を実行させてみると、 こんな音になった。 実質的に1行でサインぐらいしか見当たらないシンプルな記述なのに、である。 このプログラムでは、nが8ということで、ピッチにランダム要素を加味した8個のサインが再帰的にミックスされているのである。( var n = 8; { Mix.fill(n, { SinOsc.ar(500 + 500.0.rand, 0, 1 / n) }) }.play; ) この様子を、postウインドウに状況を表示するサンプル
で見てみると、実行したサウンドは こんな音で、 postウインドウの表示は以下のようになった。 なるほど、nが8、と指定すると、ちゃんとn=0からn=7までやっている。( var n = 8; { Mix.fill(n, { arg index; var freq; index.postln; freq = 440 + index; freq.postln; SinOsc.ar(freq , 0, 1 / n) } ) }.play; )
これで「Mix it Up」は終わったので、その次の Scoping Out Some Plots に進もう。 これはサウンドのビジュアライズ(波形を表示)というトピックのようである。
を実行してみると、「play」でサウンドが鳴る代わりに、「plot」によって以下のように表示された。{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.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 Started の Getting 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 Started の Getting 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 Started の Busses に進んだ。 ざっと読んだところでは、仮想的なチャンネルとして、サウンド出力、サウンド入力、コントロール用などの「バス」を定義して、自在にワイヤリングする、というアイデアのようである。ディジタルミキサであれぱ、よくある構造である。 すでに「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 Started の Groups からである。SuperColliderのサーバは、nodesと呼ばれるSynthの複合体であり、groupというのはオブジェクト指向的にそれらをまとめたものらしい。 groupによって、制御の順序や同時に送るメッセージを簡単に扱えると言う。 しかし、これに続く以下の例は、いまいちピンと来なかった。(^_^;)
g = Group.new; h = Group.before(g); g.free; h.free; 引き続き、以下のような例で考えてみよう、とある。 まずはlocal serverをbootして、
をenterすると、post windowには「a SynthDef」 と表示された。 続いて、( // 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には「Bus(audio, 16, 2, localhost)」 と表示された。 続いて、// 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した。これでSynthが実行されて、post windowには「Synth("tutorial_DecaySin2" : 1010)」 と表示され、 こんな音が出た。 SuperColliderらしい、いい音である(^_^)。 このサウンドは、サーバを止めずに// 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); ) で止めることが出来る。1行目でサウンドが止まり、3行目で「Bus(audio, nil, nil, localhost)」 と表示された。 さらにgroupの参照をクリアするために、~sources.free; ~effects.free; ~bus.free; をenterすると、「Environment[ ]」と表示された。 これは重要で、色々と設定した環境をクリアしないと、次第にゴミが溜まってメモリを食うので、これは必須となる。 また、このEnvironmentをnewでcreateして、それを後で復活させるためには、以下があるという。 やはりpushとpopは健在なのだった。(^_^;)// remove references to ~sources and ~effects environment variables: currentEnvironment.clear; これに続いて、addActionsとして、「addBefore」、「addAfter」、「addReplace」、「addToHead」、「addToTail」などが、以下のサンプルとともに書かれていたが、どうもここもビンと来ないのでさりげにスルーした。(^_^;)// to be sure, create a new Environment: Environment.new.push; // some code.. // restore old environment currentEnvironment.pop; この「queryAllNodes」というメソッドは、post windowに出て来る「a Server」「Group(1001)」とか「Synth 1002」などのメッセージに関係している。 サーバが起動されると、ID=0である特別なRootノードが起動され、そこからツリー構造で新しいノードが生成される。 このdefaultは以下のようである。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; グループのもう一つの大きな効用は、「set」メッセージを一斉にいくつものグループに送ることである。 以下のサンプルでは、1行目を送ってグループを定義し、次の行で同時に鳴る4音が起動される。 こんな音である。 そして3行目をenterすると全ての音量がとても小さくなり。4行目でストップする。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 この後には、以下のようなGroupのオブジェクト指向的なインヘリタンス(継承)などの話題があったが、軽くスルーした。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」はおしまいで、その次は、まだまだ地味に続くが、 Getting Started の Buffers である。 サウンドを単に生成したり、サウンド入力にリアルタイムのエフェクトを加えるだけでなく、サウンドファイルを読み込んで再生したり、ライブでサンプリングするためには、まぁバッファは当然、必要だろう。 バッファ・オブジェクトを生成してメモリを割り当てるのは、以下のようにする。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 たぶんサンプリングのフレームは44.1KHzとかだろう。 「秒」で定義するには、以下のようにすればよい。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) Bufferはallocメソッドの他に「read」クラスメソッドを持ち、これでサウンドファイルを読み込み、UGen PlayBufを用いて以下のように再生できる、という。 ただしこれでは、サウンドファイルへのパス指定が謎である(^_^;)。実行させると以下のようになった。b = Buffer.alloc(s, s.sampleRate * 8.0, 2); // an 8 second stereo buffer 「ファイルがありません」のようなメッセージが出るかと思ったら、 こんな音が鳴った。 つまりこれは、「sounds/」という指定でdefaultで探しに行くのだろう。 探してみると以下のように、アプリのSuperColliderの直下にあった。// 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;
考えてみると、SuperColliderではソースは全て完全にテキストファイルで、これをpostウインドウにコピペしては選択(ハイライト)してenterしているので、Processingなどのようにプロジェクトをどこかに保存・・・というのがあまり無いのだった。 しかし、アプリのフォルダの直下というのは一般性が無いので、何か、明示的に指定する方法があるのだろうか。 とりあえずテキストでは、「PlayBuf」を以下のように解説している。
サーチパスの解説が無いまま、次には以下のように、ストリーミングにより、大きなサウンドファイルを鳴らすサンプルである。 こんな音が鳴った。 PlayBufと違って、ファイルの再生速度を指定する自由度が無いが、こちらの方が使用メモリを削減できる。PlayBuf.ar( 1, // number of channels bufnum, // number of buffer to play BufRateScale.kr(bufnum) // rate of playback ) いろいろなインスタンスをpostウインドウで調べてみろ、というのでやってみると以下のようになった。( 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; 以下のサンプルは、「action」関数を使って同様なことを行ったものである。// 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) そしていよいよ、「PlayBuf」とペアになる、「RecordBuf」である。 以下の例では、まず「b」にrecordバッファを定義し、次に「x」のSynthDefでピンクノイズを生成して再生するとともに「b」にレコーディングし、その後で「x」を解放した後に、「b」のSynthDefでバッファを再生している。( b = Buffer.read( s, "sounds/a11wlk01.wav", action: { arg buffer; x = { PlayBuf.ar( 1, buffer, BufRateScale.kr(buffer) ) }.play; } ); ) x.free; b.free; バッファというのはデータのカタマリなので、以下の例のように「set」と「get」で、それぞれにアクセスできる。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; また、以下の例のように「setn」と「getn」で、バリューのレンジを変更/参照できる。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) 同時にset_getできるデータ数には上限(defaultでは、UDPの制限に起因する上限として1633個)がある。 これを越えたい場合には、以下の例のように、「loadCollection」と「loadToFloatArray」という2種類の方法がある。 「FloatArray」は、「Array」のサブクラスで、floatデータを持っている。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) 最後に「Other Uses For Buffers」として、ウェーブシェープのような多量のデータを格納して、 これを波形として参照するサンプルが以下である。 こんな音が鳴った。( // 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) 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秒ごとにランダムな数を表示し、無限に続いてしまう。
- Function
- Routine
- Task
// 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 Started の Sequencing with Routines and Tasks である。 これは上の項目で出て来た、残り2つのアクションである。
「Routine」とは、データ処理のアクションである。 以下の実行例でこれは簡単に判る。
この例では、格納した文字列を呼び出したが、「routine」ではデータを生成する、という重要な機能もある。 また、「clock」で「routine」をスケジューリングすると、時間データが返り値となる。 これは音楽のシーケンシングで使える。 以下のように、clockで無限ループとなったように、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 いよいよここで、表示(postln)をplayに替えて、スケジューリングによってループのフレーズを生成させてみよう。 以下のような例で、こんな音が鳴った。 これが、我々の初めてのSuperColliderシーケンス、というわけである。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; ( 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を使って発生させる。
- clock (Routine) or argClock (Task): Which clock should handle scheduling for this sequence
- doReset (Task only): If true, reset the sequence to the beginning before playing; if false (default), resume
- quant: A specification of the exact starting time
ここで、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 を実行すると、 こんな音になる。 これは8分音符で上行しているスケールに対して、4分音符の分だけ遅れてスタートしているので、 3度音程でハモっている事になる。 これを、後で合流するものをu = f.value.play(quant: [4, 0.5]); // next 4-beat boundary + a half-beat を実行すると、 こんな音になった。 これは8分音符で上行しているスケールに対して、8分音符で3個分だけ遅れてスタートしているので、 4度音程でハモっている事になる。u = f.value.play(quant: [4, 0.75]); // next 4-beat boundary + a half-beat このようなシステムを使うと、いわゆるノートシーケンスをデータroutineで実現できることになる。 以下のサンプルを実行すると、 こんな音がした。 自分としては好きではないが、まさに「打ち込み」音楽である。(^_^;)
「routine」がデータのために、「task」がplayのために使われている。 ここでは「while」が使われて、シーケンスデータの最後になるまでは無限ループ、としている。 シーケンスデータの長さは可変長なので、「while」が定番となりそうである。( 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 ); ) このセクションの最後は「A note on server messaging and timing」である。 SuperColliderでは、内部的にサーバへの転送にOSCを使っているらしい。 OSCについては ここ にも書かれている。 Synthでサウンドを生成していると、このOSCのネットワーク遅延が発生するために、ある不定の遅れ(latency)が存在する。 そこで、システムのclockに対して、サーバの遅れ「ServerTiming」まで考慮する必要がある。 以下の例は、「SynthDef」を毎秒10回、呼び出しているので、システムによっては(遅いパソコンなどの場合)、サウンドの間隔にばらつきが出るのだ、という。
しかしこのサウンドは こんな音で、ばらつきは感じられない(^_^;)。 アクティビティモニタで見てみると、以下のようにCPUはほとんど暇だから仕方ないのか。( t = Task( { loop { Synth( \singrain, [ freq: exprand(400, 1200), sustain: 0.08 ] ); 0.1.wait; } } ).play; ) t.stop;
そこで、いじわるテストとして、以下のようにQuickTime ProでMP4をM4Vに変換する、というタスクを走らせて、2つのCPUをほぼ100%にして、同じサウンドを鳴らしてみた。 しかしそれでも、 こんな音で、ばらつきは感じられない(^_^;)。
mac miniとはいえ、以下のようなスペックではちょっと差が出ないのかもしれない。
このレイテンシを気にする場合には、 以下の「makeBundle」メソッドを使うとよい、とのことだが、鳴らしたところではまったく変化が無かった。
あと1項目を残して、今日はここまでである。( t = Task( { loop { s.makeBundle( s.latency, { Synth( \singrain, [ freq: exprand(400, 1200), sustain: 0.08 ] ); } ); 0.1.wait; } } ).play; ) t.stop; 2011年3月5日(土)
昨日は Processing日記 を進めたが、今日は午前に沖縄ミーティングがあり、午後にはゼミのちひろちゃんがシステム返却のついでにiMacセットアップを手伝ってくれるので、またまた時間がほとんど取れない。 明日3/6は停電工事のSUACネット停止のためお休み、そして3/7-10は沖縄、3/11は終日学科会議、3/12は一般後期入試、と予定満杯で、次は卒業式の週からのスタートとなりそうだ。 Getting Started も残り1項目となったので、区切りのためにも、最後の Sequencing with Patterns だけは見ておくことにしよう。ここではシーケンシングを「パターン」を用いて、データストリームとして簡単に扱う。 以下の例では「Over the Rainbow」のメロディー音列が生成されている。
「Pseq (Pattern-sequence)」を使うと、同じように、以下のようになる。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 これを用いると、「Over the Rainbow」のメロディーを生成するSuperColliderプログラムは以下のようになる。 こんな音がする。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 ( 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をネスティングさせて、次第に拡大する幅でのりランダムを生成させたものである。
- Pseq(list, repeats, offset) -- return the list's values in order
- Pshuf(list, repeats) -- scramble the list into random order
- Prand(list, repeats) -- choose from the list's values randomly
- Pxrand(list, repeats) -- choose randomly, but never return the same list item twice in a row
- Pwrand(list, weights, repeats) -- like Prand, but chooses values according to a list of probabilities/weights
- Pseries(start, step, length) -- arithmetic series, e.g., 1, 2, 3, 4, 5
- Pgeom(start, grow, length) -- geometric series, e.g., 1, 2, 4, 8, 16
- Pwhite(lo, hi, length) -- random number generator, uses rrand(lo, hi) -- equal distribution
- Pexprand(lo, hi, length) -- random number generator, uses exprand(lo, hi) -- exponential distribution
- Pn(pattern, repeats) -- repeat the pattern as many times as repeats indicates
- Pstutter(n, pattern) -- repeat individual values from a pattern n times. n may be a numeric pattern itself.
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では、以下の例のように、パターンをランダムに入れ替えて、これを呼び出すたびにさらにシャッフルする事もできる。
より簡単にシーケンシングを実現するために「Pbind」がある。 これを使って「Over the Rainbow」のメロディーを生成するSuperColliderプログラムは以下のようになる。 ただしオリジナルテキストのサンプルにはいくつか教育的なバグがあり、エラーが出るので注意(^_^;)。 こんな音がする。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 ] ( 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」のドキュメントを参照する必要があるが、ここではおよそ、以下のような処理を実現している。
最後に以下のような例があって、最初のチュートリアルは終わりとなる。 これを「楽譜」として読めるのは・・・いつの事だろう(^_^;)。 まず、前半部分として以下のプログラムを走らせると、 こんな音がした。
- 「Pbind」は、名前と値をもちノートを生成するためのパターンをイベントとして発生する
- 「Pbind」は、「name, pattern」のペアを読み込み、ストリームとしてメロディーを生成する
- 音源については、「add」メソッドによって、SuperColliderのsynthを呼び出す
- 「delta」という値が、イベントの間隔時間を示す
そして、後半部分として以下のプログラムをさらに走らせると、 こんな音になった。( 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); ) これでようやく、 Getting Started が終わりとなった。 ここまでを「日記(1)」として区切りにして、次は「日記(2)」として Workshop materials for Computer Music (G6002) から始めてみたいと思う。p.stop; k.stop;
SuperCollider日記(2)
「日記」シリーズ の記録