並行性
ChucKでは複数のプロセスを並行に実行することができる。このおかげで別々に作ったメロディやパートを同時に鳴らすことができる。ChucKのVM上で動くプロセスのことをshredと呼ぶ。miniAudicleでshredを並行に動かすには、いくつかのファイルを開いて、それぞれ"Add Shred"を押していけばいい。"Add Shred"を押すごとに対応するファイルからshredが作られて並行に実行される。でも、こうやって手動でやると操作のタイミングを正確に合わせることは難しい。完全な同時押しとかマルチタッチパネルでも無理。
ということで正確なタイミングで処理させたいときは、shredをプログラムから操作(追加、削除、置き換え)する。この方法には二種類ある。
1.標準ライブラリMachineを使う
MachineはVMのインタフェースで、shredを操作するための関数が用意されている。前もって保存しておいたプログラムから新しくshredを作って追加・実行するには関数addを使う。返り値はshredのID*1。
Machine.add("foo.ck"); // ファイル"foo.ck"からshredを作って追加
addを呼び出したshredとaddで追加されたshredの間に親子関係はないので、呼び出し側のshredが削除されたり終了しても、作られた側は何の影響も受けない。
だから前日のメモでちょっと触れたThe ChucK Tutorialの「Concurrency in ChucK:」に載っている、コマンドラインからの命令によるファイルの同時起動
Run all three in parallel:
%> chuck moe.ck larry.ck curly.ck
と同じことをminiAudicleの環境でやるには、新しい編集ウィンドウを開いて
Machine.add("moe.ck"); // どうでもいいんだけど、 Machine.add("larry.ck"); // こいつらのファイル名の、 Machine.add("curly.ck"); // 理由がわからない
とだけ書いて(それとカレントディレクトリを設定して)、"Add Shred"すればいい。
追加以外では、実行中のshredのIDが分かっていれば削除や置き換えもできる。
Machine.remove(id); // idのshredを削除 Machine.replace(id, "bar.ck"); // idのshredを"bar.ck"に置き換え
2.キーワードsporkを使う
shredを作って追加することをsporkと呼ぶ。sporkは元の意味は「spoon + fork」で先割れスプーンのこと。UNIXのシステムコールforkっぽい感じだと思うけど、なんでsporkにしたかというと、shredと頭韻を踏んだんだと思う。多分。happy holidaysみたいな。違うかも。
キーワードsporkはMachine.addとは違って関数単位でshredを作る。それと注意しなきゃいけないのは、sporkの返り値は作ったshredへの参照であって、shredのIDやsporkした関数の返り値ではないということ。今のところsporkした関数からの返り値は取得できない。
fun int test(int x) { <<<x, " in test function">>>; return x; } <<<test(3)>>>; // 単なる関数呼び出し <<<spork ~ test(8)>>>; // sporkで関数からshredを作る 1::samp => now; // 親の終了を少し遅らせる
「~」が何かはこういうものだと思って気にしてはいけない。上のコードのコンソール出力は、
3 in test function
3 :(int)
74048016 :(Shred)
8 in test function
となった。
sporkで追加されたshredは呼び出し側と親子関係を持つので、呼び出し側が削除されれば追加された方も削除される。上のコードの「1::samp => now;」は、作られたshredが実行される前に親が終了してしまうのを防ぐために必要で、この行がないとコンソール出力の「8 in test function」は表示されない。それから、sporkされた子shredは親shredのグローバル変数にアクセスできる。
カラフルでなくてもいいから音を鳴らしたくなったのでまたFizzBuzzを。アイデアないなー。耳で聴くFizzBuzzもどき。これを実行するとFizzBuzzに短い周期があることが聴覚でわかる。
Shakers s => JCRev r => dac; .1 => r.mix; 200::ms => dur T; // 自分で定義して単位時間のように使う fun void inter(int i, float f) { Wurley w => r; // 電子ピアノ f => w.freq; while(i::T => now) w.noteOn(.5); } spork ~ inter(3, 220); // Fizzだと思って spork ~ inter(5, 440); // Buzzだと思って 3 => s.preset; // ギロという打楽器 for (int note; note < 128; note++) { // 音階(0〜127) Std.mtof(note) => s.freq; s.noteOn(1); T => now; }
親shredがTごとに音階を上っていく間、二つの子shredが3Tと5Tごとにそれぞれ220、440Hzの音を鳴らす。shredを同時に作成することで、親も子もお互いの情報を知らずに勝手に自分のタイミングで鳴っても上手くいく。例えば15Tごとには異なる三音が同時に鳴る。
初めて使うユニットジェネレータについてメモ。ChucKにはSynthesis ToolKit(STK)という音響用のAPIを基にしたユニットジェネレータがいくつか用意されている。ShakersやWurleyはSTKの楽器系のユニットジェネレータ。SinOscのようにつないだらその瞬間からずーっと音が垂れ流しになるわけではなく、楽器なので弾かないと鳴らないし弾いた音は減衰していく。鍵盤を弾くことに対応するのがnoteOn(float velocity)で、引数のvelocityで音の強さを指定できる。
*1:将来、shredへの参照を返す仕様に変わるかもしれないらしい