前回投稿でさらっとスルーしていました「"fork"を使ってファンクションを評価したときにTempoClockで再生される」 と書きましたが、スケジューリングで大事なクロックと"play"について少し解説します。
スパコには”play”といういろいろなオブジェクトと合わせて使うと、プロセスを再生できる便利なメソッドがあります。
前回使用したのはRoutineとTaskですね、これはStreamオブジェクトに対しての”play”でした。forkもファンクションに対して使っていますがRouineの省略表記なので、Streamとして再生しています。
そのほかのplayにはたとえばFunction.play ( {}.play )、SynthDef.playなどがあります。これはシンセを生成して、サーバにノードを一つ作る(シンセを鳴らす)までやってくれます。x = { SinOsc.ar(1200,0,0.3) }.play;
x.free;
( x = SynthDef("test-dayo",{ var trig,src; trig = Impulse.kr(MouseX.kr(0.5, 20, 1)); src = SinOsc.ar(Sweep.kr(trig, 700) + 500, 0, 0.2); Out.ar(0,src); }).play )
x.free ノードを生成したので.freeです。
他にも様々なクラスに対して"play"が用意されていますが、基本的に何かのプロセスの再生をするという意味です。
さてStreamとしてスケジュールを再生した時に大切なのがクロック(Clock)です。その名の通り時計です。SuperColliderAppではいくつかの種類のクロックを利用しています。
SystemClock:システムクロック。
AppClock:アプリケーションクロック。 TempoClock:テンポクロック。
主にこの3つです。それぞれSuperColliderAppが立ち上がった時からカウントアップしています。
特徴ですがSystemClockがAppClockよりも正確だそうです。TempoClockはSystemClockと似ていますが、時間を秒やテンポ(bps,bpm)で管理出来ます。(じゃあAppClockいらないじゃん、て感じですが必要な場面もあります) 通常何も引数を加えないplayで再生されたものはTempoClockで再生されます。
通常意識しなくていいのですがスパコでは”play”すれば大体TempoClockで時間を刻みます。 TempoClock自体もオブジェクトなのでいくつでも任意に作る事ができます。
t = TempoClock(1); // create a TempoClock tに割り当てたTempoClockでRoutineを再生してみましょう
( p=Pseq([ "hello routine", 1, 20 ],inf).asStream; r = Routine({ loop{ p.next.postln; 1.0.wait; } }).play(t); ) ここでtのテンポをかえてみます、メソッドは.tempoです。(単位はbeat par a second、一秒簡のビートの数です)
t.tempo = 2 二倍の速さで再生されましたか。loopの中に書いた1.0.waitもTempoClockにい依存していることが分かります。 因にTempoClockのデフォルトのテンポは1、BPMで60になっています。
TempoClockは自分で生成しなくてもSuperColliderApp立ち上げ時にグローバルに一つ立ち上がっていますので”play”するだけで再生されます。デフォルトのクロックのテンポを操作する場合はTempoクラスを使います。
Tempo.tempo = 2; //tempoの単位はbps
Tempo.bpm = 140; //bpm
*AppClockが必要なときですが、ヘルプにあるように他のクロックではCococa primitiveが呼び出せないとあります。 Cococa primitiveはCococaを使ったクラスなので、たとえばGUIクラスのスケジューリングにはAppClockで再生する必要があるということです。
よくあるのが音のシーケンスに合わせてボタンを点滅させたい時。MIDI経由でフィジカルコントローラの値をGUIのスライダに反映させたい時など、GUIのスケジュールをAppClockで再生する必要がありまます。 その時どうしてもTempoClockやSystemClockで再生中にAppClockでGUI動かしたいときが出てくると思います。そういった場合はファンクションにdeferをつけるとそのブロック内だけAppClockで再生されます。
defer{ //GUIの更新とか }
という疑問を持つ方が多くおられるようなので、 何かしら役に立てればと思い以下にメモしていきます。 といってもわたしもヘルプを読み読み勉強しつつなのですべてを鵜呑みにしないように!
sclangでコードが評価されるタイミングがよく分からないと思いますが、基本的には選択してenterキーを押したときです(うーん奇妙ですね)
それ以外だとstartup.rtfに書いておくとSuperColliderAppの立ち上げ時に評価されるとか、OSCResponderやMIDIInとかにハンドラを用意しておいた時とかでしょうか..。
ではどうやってスケジューリングを書くのかと。
sclangにはスレッドとして機能するRoutineやTaskなどのクラスが用意されています。こうしたクラスにループのスケジュールを書いて”play”でインスタンス化してあげれば 周期的に評価されます。 *周期的に評価するにはdo()やloop()等のリピーターが必要です。 また全体がオブジェクトということで変数に割り当てて管理出来るのも特徴です。
まずはRoutineを使ってみます。( r =Routine({ "hello routine".yield; 1.yield; 20.yield; }); )
r.next; 一度しか評価されませんでしたか、もう一度r.next;を選んでenterで実行してください。5回繰り返します。
----実行結果---- hello routine 1 20 nil nil
.yieldイールド(プログラミング的にはイールド文といってなんか渋い文らしい)をつけると、オブジェクト内で値がどこまで再生されたか保持しておく事ができます。.nextや.resetなどで次の値を呼んだり初期化したり出来ます。値が空になるとnilを返します。
Routine内にyieldの値を埋め込むこともできますが、ややこしいので今度はPatternクラスを使ってみましょう。リストを順番に呼び出すPseqクラスを使いましょう。
p=Pseq([ "hello pattern", 1, 20 ]).asStream;
p.next ----実行結果---- hello pattern 1 20 nil nil
.asStreamというのがめんどくさいですね。Patternオブジェクトに直接.nextとはできないのです。一度Streamオブジェクトにしないと呼び出せないようです。Streamは先ほどあった.yieldと同じ"オブジェクトがどこまで再生したかを保持しておく"という機能を持っています。(RoutineもStreamクラスの派生なのです)
さて、Streamを使ってのシーケンスはできました。
これを自動化するには.nextや.resetなどを定期的に呼べばいいのではないでしょうか。
スパコには便利なplayというメソッドがあります。
Streamを"play"でインスタンス化します。その際、ファンクションに.do()とかloop()とかをつかいます。次のループに入るまでの待ち時間を入れます。.waitを使います。これを入れないと最悪SuperColliderAppがハングします。今度はTaskクラスを使ってみましょう。
( a = Task.new({ loop({ "ok, that was it".postln; 1.0.wait; }) }); )
a.play; a.stop; TaskはRoutineとほぼ書式は同じです。違うのはstopで止めた後、startで再開出来るという便利なところです。ただ注意があってクロックとの関係がどうにかこうにかなので心して使えと書いてありましたが詳しくはヘルプの最後の方をみてください。
RoutineからPatternを呼ぶこともできます。
( p=Pseq([ "hello routine", 1, 20 ],inf).asStream; r = Routine({ loop{ p.next.postln; 1.0.wait; } }).play; )
r.stop; *実はRoutineはもっと簡単に書けます。
FunctionクラスにはforkというメソッドがあってこれをつけとくだけでRoutineとしてTempoClockで再生されます。
( fork { inf.do({arg i; i.postln; 1.0.wait; }) } ) そのかわりforkはたとえ変数を割り当ててもプロセスをstopで止めることはできません。コマンド+.で止めましょう。 あと.waitなどの時間を与えないと実行時ハングすると思います、必ず入れましょう。
さてこのようにスケージューリングは書くことがきました。
しかし実際シンセにさまざまな値を渡しつつ、いくつものパターンを同時に作りたいとなった時、もっとたくさんのパターンを簡潔に書きたくなるでしょう。
PではじまるPatternクラス郡にいろいろな機能を持ったクラスがあります。
つづく..
暑いのでなんか温度が測りたくなったので、 太陽光パネル、温度センサー、cdsのついたセンサーを置いてPachubeにフィードを流しています。
http://pachube.com/feeds/12112 太陽光パネル自家発電用ではないです、単に発電している電圧を測っています。
以前Xbeeを使ってあまっていてました。で、もったいないので、APIモードで間にマイコン使わずフィジカルコントローラとして使えないかとおもったので自分用メモ。
以前はATモードで単にシリアルをスルーさせるだけだったので何もしないでも動いたけど、APIモードダととたんに設定項目が増えて最初分からなかった。
今回は秋月の三軸加速度センサーを使用。xyzアナログ値をXbeeのAPIモードでAD変換して親機に送りPCのシリアルポートと通信させる。電源は3.7vのボタン電池で駆動。
以下メモ。 ・子機はサンプル周期の設定(コマンドIR)をすれば勝手にAPIモードに ・その際親機のAPIモードはEnableに ・今回D0~D2ピンをADモードで使うのでD0-D2コマンドで2(ADC)に ・AD値のリファレンスボルテージをVREF Pin (14pin)に落としてあげないとAD値が正しく出ない(コマンドAVでインターナルのリファレンスに設定すれば必要ないっぽい)今回は3.3v ・0x92コマンドなんぞ ・X-CTU無くても設定コマンド手打ちで設定出来るけどいったんAPIモードに設定した後つらい
3.7vのボタン電池のはずが5v近く出ていたので仕方なくレギュレータ回路を挟む事に。 Xbee<>USBエクスプローラを使ってるのはピッチ変換買うのがめんどくさかったから、このへんもっと小型にできるはず。
送られてくるUART dataの内容とかはまた後で、有効にしたピンの数で長さが可変するみたい。