byforte前回、前々回でUGenという出口から始まり、ようやくクライアント側からサーバ側への橋がみつかった。いままでの道のりを図示すると左図のようになるだろうか。今回見ていくのはもう一つの橋、Synthクラスだ。binaryにしてサーバに送り込んだら、次はそれをシンセサイザノードに実体化しないといけない。サーバにはこのためのコマンドがあり、それが/s_newだ。nameはSynthDefで指定したシンセサイザの名前だ。名前は一つのSuperColliderサーバ空間内でユニークである必要がある。ネームスペースなどはないため、うっかりとバッティングすることもあるかもしれない。binaryが上書きされる。binaryは言うなればシンセサイザノードのメタクラスなので、それが変わったからといって既に生成されているインスタンスが変わってしまうということはない(既に作成されたシンセサイザノードは変更されない)。synthIDには作成するシンセサイザノードのIDを指定する。この値が-1だった場合にはサーバが自動的にIDを生成する。ここで一つサーバ内にお約束があって、シンセサイザノードを操作する他のコマンドで対象のIDが-1だった場合、一番最後に作成されたシンセサイザノードのIDを参照するということだ。IDがバッティングした場合はどうだろうか。それは、サーバのソースを見れば分かる。存在した場合はエラー終了}ノードを作成するときに指定IDが既に存在するかをチェックし、存在した場合はエラーを返すという実装になっている。前半部分に先ほどのIDに-1を指定した場合の処理が書かれている。actionの操作の対象ノードの指定だ。scserver内には初期的にTopGroupというグループノードのみが存在し、そのIDは0になっている(sclangの環境ではまた違うのだけど、それは後述する)。ノードを追加したいけどそのためにはノードが必要という循環依存関係は回避されている(少なくともTopGroupのIDを指定することが出来る)。actionはどういう風に追加するかだ。追加方法は次の5種類ある。0では指定されたグループの先頭に、1では指定されたグループの最後尾に、2では指定されたノードの直前に、3では指定されたノードの直後に、4では指定されたノードと入れ替えて、元あったノードは削除される。操作から見ても分かるように、0と1では指定するターゲットノードの種類はグループでないといけない。前々回に見たとおり、ノードの順序は実行順序に関係があるということには注意しておく必要があるかもしれない。/s_newコマンドの残りのパラメータはSynthDefで指定したコントローラに設定する初期値となる。必要無い場合は省略できる。/s_newコマンドは4つ以上のパラメータを持つのだけど、実は最低限必要なのは最初の二つだけだ。kSCErr_SynthDefNotFound;}msg.geti()でint型のパラメータを取得しているのだけど、この関数はOSCパケットにそれ以上データがない場合(例えばパラメータが2つしかないパケットに対して3つめを取得しようとしてる場合)には0が返ってくる仕様となっている。/s_newのコマンドの3つめ4つめにこの処理が適用されるとすれば、addAction=0でグループの先頭に追加、そしてターゲットグループID=0は前述のTopGroupになるからだ(ターゲットノードIDの取得部分のソースはこの範囲にはないが同じ処理を行っている)。Synthクラスサーバとのシンセサイザノード関係のやりとりをラップするSuperColliderの言語側でのクラスだ。このクラスに対して操作を行うと、それは透過的にサーバへのコマンドに変換され送信される。Call)ということだ。Synthオブジェクトにパラメータを設定するメソッド、setはSynthクラスの基底クラス、Nodeクラスでこんな風に実装されている。//"/n_set"}Synthクラスはご存じのとおりとても簡単な手続きでシンセサイザノードを作成することが出来る。簡潔に書こうと思うとSynth(“sine”)のような記述で済んでしまうのだけど、その中ではどのような処理が行われているのだろうか。前項で/s_newの動作を見てきた。/s_newには最低二つパラメータが必要だ。binaryの名前となる。IDは-1だった場合自動的に割り振られるというお約束を思い出すと、ではSynthクラスのコンストラクタでIDを指定しない場合は-1を渡してるのではないか、と一瞬考えるのだけど、それではうまくいかない。-1でサーバで自動的にIDを割り振った場合、クライアント側ではそれが何番に振られたか分からないので継続的使用が出来なくなってしまうのだ。シンセサイザノードが1つしかない場合には、前述のお約束で-1でアクセスできる。Synthクラスにはそういう制約はなさそうだ。やって実現してるのだろうか。※実はSynthクラスにも作りっぱなしで後は放置しても良い(言語側からのアクセスは考えなくても良い)という用途のシンセサイザノードを作成するgrainというメソッドがある。Node.scにあるgrainの実装を見てもらえば分かるようにこのクラスメソッドの戻り値はnilで以降の継続操作を想定していない。いうことで、Synthクラスのコンストラクタを見てみる。inTarget.nodeID,*(args.asOSCArgArray));^synth}おおざっぱに言って、いろいろと設定をして最後にサーバへ/s_newを送っている。新しく作るノードのIDは/s_newの二つ目のパラメータなので、そこを見てみるとsynth.nodeIDとなっている。synthはというとその前にthis.basicNew()で作成されている。Synthクラスのインスタンスは生成されていて、IDもまた決まっているようだ。basicNew()は最終的にSynthクラスの基底クラス、NodeクラスのbasicNewメソッドがよばれる(SynthクラスのbasicNewはスーパークラスのbasicNewを呼んだ後にインスタンス変数defNameを設定しているだけだ)。server)}nodeIDが指定されていない(=nil)場合、ServerクラスのnextNodeIDというメソッドでIDを取得している。ServerクラスのnextNodeIDの実装。options.initialNodeID);このNodeIDAllocatorというクラスが結局のところIDを割り振ってるようだ。このNodeIDAllocatorクラスの実装はEngine.scにある。mask}tempは指定しない場合初期値1000で、毎回1ずつ足したものをIDとして返している。この番号は見覚えのあるものではないかと思う。最大値は0x3ffffff(67108863)でそれを超えるとラップアラウンドする。そんなにIDを使う事なんて無いだろうけど。ここでなぜ最大値が0x7fffffffでないかというと、実はSuperColliderは最大32ユーザまでの同時操作を想定してるのだ(クライアント側の実装として)。符号を除いた31ビットのうち上位5ビット(0〜31)をユーザー識別子として最後にorしたものがIDとして返ってくる。allocの最後でorされているmaskというのがそれだ。使うID空間を最後にorするフラグで分けて、1つのサーバで同時に32人までのプレーヤでセッションを行うことが出来るというわけだ。clientID)}この4番目のパラメータ、cliendIDに入れれば良い。32以上の値を入れるとNodeIDAllocatorのコンストラクタがエラー終了(nil)する。マイナスの値はチェックされてないのでやめておいた方が無難そうだ。IDがどうやって取得できたか分かったところでSynthクラスのコンストラクタに戻る。actionの番号に変換される。ここには番号をキーとした値も格納されているので、引数としてはシンボルでも数字でも通るようになっている。IDの部分はinTarget.nodeIDとなっている。inTargetはtarget.asTargetなのだけど、これはデフォルトパラメータがないので指定しない場合はnilになっているはずだ。いうことで、NilクラスのメソッドasTargetを見てみる。このメソッドはasTarget.scにあって、後から追加されている形になっている。}これはID=1のグループノードということになる。何も指定しなかった場合、シンセサイザノードはこのGroupノードの下にぶら下がることになる。ところで、ID=1のグループノードなんて誰がいつ作ったのだろうか?サーバで初期的に作られるのはID=0のグループノード、TopGroupだけだったはずだ。実はこれは、先ほど出てきたServerクラスのメソッドinitTreeの続き部分にその答えがある。1);tree.value(this);ServerTree.run(this);}見てのとおり、ID=1を指定してグループノードを作成している(サーバに/g_newコマンドを発行)。一つ注目すべきポイントは、/g_newの2つめ3つめのパラメータが省略されてることだ。false;this.initTree;サーバが起動したら呼ばれるようになっているのだ。サーバを起動すると(Server.boot)ID=1のグループノードを作成して、以降その下でシンセサイザノードを扱うというお約束になっているようだ。Synthクラスだけど、そのsclangの環境外から、例えば別のユーザのsclangとかサーバに直接コマンドを送って作ったノードを操作したい場合はどうすればいいのだろうか(多クライアント対1サーバでセッションを行ってるときに他のプレーヤの作ったシンセサイザノードに乱入する、とか)。それには通常のコンストラクタではなくbasicNewの方を使う。このメソッドはSynthクラスのインスタンス生成は行われるのだけど、サーバに/s_newの発行は行われない。ノードIDは整数でのIDを使用する。Synthクラスのメソッドを使う必要がなければ、直接Nodeクラスのインスタンスとして作成しても良いだろう。このインスタンスはノードに対するアクセスを提供するだけで、そのノードがどういう関係性にあるかは再現されない(そのノードを作ったユーザの環境で、そのノードがどのノードの下にぶら下がってるか、など)。※調べられないというわけではなく、自動的には再現されないということ。環境外で作ったものでなくても、自分の環境で作ったシンセサイザノードでも、この方法でインスタンスを生成することが出来る。それは例え環境内にあったとしても(そのIDのSynthオブジェクトが存在したとしても)、元のオブジェクトとのマッチングは行われない(別オブジェクトとなる)。親子関係などが反映されないのも同じだ。ところで、ID参照型の(/s_newを送信しない、basicNewで作成した)オブジェクトにデフォルトパラメータでfreeメソッドを呼ぶと、サーバに/n_freeが送信され、サーバ空間で解放されていなくなってしまうので注意が必要だ。freeの引数にfalseを渡してやれば/n_freeの送信は行われないという実装になっている。Themegraphy。