/**/

チュートリアル 51:
Jitter における Java

このチュートリアルは、読者が mxj Java クラスプログラミングのプロセスについての知識があることを前提としています。これがどのように行なわれるかについての詳細は、「Writing Max Externals in Java」というドキュメントで述べられています。

このチュートリアルでは、 mxjクラスが直接入力マトリックスのセルを操作する方法についての概要を示します。また、内部的にJitter オブジェクトをロードし、処理ネットワークを定義し、実行するようなクラスを、どのように Java プログラミング言語で作るかについても見て行きます。最後に、「リスナー」を描画コンテキストに結びつけ、マウスイベントに対してウィンドウでポーリングを行なう方法によって、ハードウェアアクセラレータの利点を生かしたユーザインターフェイス要素を設計する方法について学びます。

Jitter の Java API は、JitterObject クラスに集中しています。これは、mxj クラスに対し、Jitter オブジェクトをインスタンス化する手段を提供します。コードの次の行は、jit.op Jitter オブジェクトを生成するものです。

JitterObject jo = new JitterObject(“jit.op”);

JitterObject の call() または send() メソッドを使うことによって、Java コードの中でオブジェクトにメッセージを送信することができます。例えば、上記の jit.op のインスタンスに演算子をセットする場合には、次のように行ないます。

jo.send(“op”, “+”);

また、代わりに、setAttr() メソッドを使って、このアトリビュートを設定することもできます。

jo.setAttr(“op”,”+”);

JitterMatrix クラスは、Jitterオブジェクトを拡張します。結局のところ、jit.matrix は JitterObject です。jit.matrix はこのように共通なオブジェクトであるため、その生成と消滅のための便利なメソッドを持っていることによって扱いやすいものとなっています。例えば、コードの次の行では、4 プレーンを持ち、char 型で 320 x 240 の大きさの、新しい JitterMatrix を作っています。

JitterMatrix jm = new JitterMatrix(4, “char”, 320, 240);

JitterMatrix オブジェクトを、マトリックス名の指定によって生成することもできます。

JitterMatrix jm = new JitterMatrix(“Stanley”);

Stanley という名前を持った jit.matrixオブジェクトがすでに存在している場合、この Java 内のJitterMatrixオブジェクトはそれを参照します。この場合、新しい jit.matrixオブジェクトは生成されません。

これらの利便性も素晴らしいのですが、JitterMatrix クラスが存在する主な理由は、マトリックスセル内のデータにアクセスするためのネイティブなメソッドを提供することです。チュートリアルパッチを開いて、この動作の例を見てみましょう。

入力マトリックスへのアクセス

・Jitter Tutorials フォルダの中のチュートリアルパッチ 51jJava.pat を開いて下さい。 matrix info パッチャーをダブルクリックして下さい。 button オブジェクトをクリックして、Max ウィンドウへの出力を見て下さい。


Max ウィンドウに出力された、マトリックスについての情報

このサブパッチャーでは、button オブジェクトをクリックすると、jit.noise オブジェクトは 4 プレーンで 64 x 48 の float32 マトリックスを j51matrixforexample クラスをロードしている mxj オブジェクトに送ります。この Java クラスは、入力されたマトリックスに関するいくつかの基本的な情報をアウトレットから出力します。このクラスのコードを調べてみましょう。

import com.cycling74.max.*; import com.cycling74.jitter.*; public class j51matrixinfoexample extends MaxObject { public void jit_matrix(String s) { JitterMatrix jm = new JitterMatrix(s); outlet(0,"name", jm.getName()); outlet(0,"planecount", jm.getPlanecount()); outlet(0,"type", jm.getType()); outlet(0,"dim", jm.getDim()); } }

このクラスには単体の jit_matrix メソッド があります。当然のことながら、マトリックスはJitter オブジェクト間では名前の参照として渡されるため、入力されるマトリックスはシンプルな2つの要素からなるリストであると考えられ、これが mxj がマトリックスを見る方法になります。jit_matrix メッセージのアーギュメントは、もちろんマトリックスの名前です。そのため、私たちのメソッドが最初に行なうことは、コンストラクタの持つアーギュメントとしてだけ入力されるマトリックスの名前で、新しい JitterMatrix を作ることです。作られた jitterMatrix オブジェクトは、それと同等な、名前をつけられた jit.matrix オブジェクトを所有します。コードの次の4行は単に、JitterMatrix クラスのメソッドを使って行なうことができる、いくつかの基本的な問い合わせの結果を出力しているだけです。

マトリックス上での操作

matrix infoサブパッチャーを閉じて下さい。striper サブパッチャーを開いて下さい。

この striper サブパッチャーは、入力マトリックスのデータ上で操作を行なうクラスの例を示してくれます。2つのクラスが準備されていて、Ggate オブジェクトによってそれらの間を切り替えることができるようになっている点に注意して下さい。同じ操作を実行する2つの異なった方法を比べて、どちらがより効率的かを見て行きましょう。

toggle ボックスをクリックして、qmetro オブジェクトをスタートさせて下さい。


Jitter マトリックス上での「ストライピング」効果

これらのクラスは、入力されるマトリックスに対して、マトリックスの個々の行への横断的な繰り返し処理を行ない、いくつかのセルの値を上書きすることによって、「ストライプ」処理を行ないます。どのセルが上書きされるかは、on および off アトリビュートの値によって決定されます。on アトリビュートが値 v を持ち、off が値 w を持っている場合、このアルゴリズムは v 個のセルを上書きし、w 個のセルには書き込みません。同様にして、これをマトリックスの行方向に繰り返します。

j51matrixstriperA のコードは次のようになっています。

import com.cycling74.max.*; import com.cycling74.jitter.*; public class j51matrixstriperA extends MaxObject { JitterMatrix jm = new JitterMatrix(); int frgb[] = new int[] {255, 255, 255, 255}; int on = 2, off = 1; j51matrixstriperA() { declareAttribute("frgb"); declareAttribute("on"); declareAttribute("off"); } //このメソッドが 2D の char マトリックスを前提としている点に注意してください! public void jit_matrix(String s) { jm.frommatrix(s); int dim[] = jm.getDim(); int count = 0; boolean notoff = true; for (int i=0;i<dim[1];i++) for(int j=0;j<dim[0];j++) { if (notoff) jm.setcell2d(j, i, frgb); if ((notoff &&(++count > on))||(!notoff&&(++count > off))) { count = 0; notoff = !notoff; } } outlet(0, "jit_matrix", jm.getName()); } }

jit_matrix メソッドの中で、マトリックスの大きさを求め、それを配列 dim に格納するために、getDim()メソッドを使っています。この dim は繰り返し処理を行なうfor() ループの終端条件として使われます。setcdll2d メソッドは、マトリックスの任意のセルに直接値をセットすることを可能にしてくれます。処理が終了した時、jit_matrix メッセージを、JitterMatrix の名前をアーギュメントとして送信します。

Ggateオブジェクトを前後に切り替えて下さい。どちらのクラスが速いでしょうか?

j51matrixstriperA と j51matrixstriperB の間の違いは jit_matrix メソッドの内容だけです。次は、j51matrixstriperB のjit_matrixmethod です。

//このメソッドが 2D の char マトリックスを前提としている点に注意してください! public void jit_matrix(String s) { jm.frommatrix(s); int dim[] = jm.getDim(); int count = 0; int planecount = jm.getPlanecount(); int offset[] = new int[]{0,0}; boolean notoff = true; int row[] = new int[dim[0]*planecount]; for (int i=0;i<dim[1];i++) { offset[1] = i; jm.copyVectorToArray(0, offset, row, dim[0]*planecount, 0); for(int j=0;j<dim[0];j++) { if (notoff) { for (int k=0;k<planecount;k++) row[j*planecount+k] = frgb[k]; } if ((notoff &&(++count > on))||(!notoff&&(++count > off))) { count = 0; notoff = !notoff; } } jm.copyArrayToVector(0, offset, row, dim[0]*planecount, 0); } outlet(0, "jit_matrix", jm.getName()); }

j51matrixstriperB の jit_matrixメソッドは、1回にマトリックスの1つのセル毎に値をセットするのではなく、JitterMatrix の copyVectorToArray メソッドを使ってマトリックスから行全体を取得し、行に適当な値を上書きした後、JitterMatrix の copyArrayToVector メソッドを使って行をマトリックスに書き戻しています。気付いたかもしれませんが、クラスのこのバージョンは、マトリックスのセルを1つずつセットするバージョンに比べて、極めて速く動作します。これは、次に述べる、覚えておくべき重要なことについての優れたデモンストレーションです。C と Java の境界線を横切ることは、非常に重い処理となります。このオブジェクトの setcell を使ったバージョンでは、C と Java の境界線上を、マトリックスの全てのセルごとに行ったり来たりしなければなりません。後のバージョンは1行あたり2回この境界を行き来します。オブジェクトのこの2つのバージョンを試してみた場合、処理量の節約の度合いは明白です。

copyVectorToArray および copyArrayToVector メソッドは、全ての関連するデータ型をカバーするシグネチャによる負荷をかけられます。上記のコードを注意深く調べることによって理解することができますが、これらのメソッドは多重送信されるプレーンのデータと共にJava 配列を提供し、期待します。そのため。全てのセルのデータは隣接したものとして表されます。Java の中で単一プレーンのデータを移動させるために、 copyVectorToArrayPlanar および copyArrayToVectorPlanar というメソッドも存在します。

Jitter のマトリックスを処理する mxj クラスを使う場合、「処理を奪う」機能は組み込まれていません。そのため、オブジェクトネットワークの駆動には、常に qmetro オブジェクト、あるいは、処理が残ることを防ぐためにイベントを遅延させる何らかの構造を使うことが重要です。

入力データのコピー

上記の例では、内部的に JitterMatrix を使う方法の違いにも気付いたかも知れません。matrix info の例では、新しい JitterMatrix を入力マトリックスを受け取る度に作っていたのに対し、ここでは、JitterMatrix の1つのインスタンスを格納しておき、その中へ、毎回 frommatrix メソッドを使って入力データをコピーしています。なぜこのようにしたのでしょうか?

qmetro を停止させ、striper というサブパッチを閉じて下さい。why copy? というサブパッチを開いて、qmetroを オンにして下さい。


なぜコピー?

このサブパッチの trigger オブジェクトは、最初に mxjクラス j51whicopy のインスタンスに jit.noise オブジェクトの出力を送り、これが jit.pwindow オブジェクトに出力されます。その後、同じマトリックスが別の jit.pwindow オブジェクトに送られます。

j51whycopy の Java コードを調べてみましょう。

import com.cycling74.max.*; import com.cycling74.jitter.*; public class j51whycopy extends MaxObject { JitterMatrix jm = new JitterMatrix(); boolean copy = false; j51whycopy() { declareAttribute("copy"); } public void jit_matrix(String inname) { // 通常の環境では、このマトリックスを1回だけ作ります jm = new JitterMatrix(); if (copy) { jm.frommatrix(inname); } else //!コピー { jm = new JitterMatrix(inname); } zero(jm); outlet(0, "jit_matrix", jm.getName()); } // このメソッドは、マトリックスが char 型であることを前提としている点に // 注意してください! private void zero(JitterMatrix m) { int z[] = new int[m.getPlanecount()]; for (int i=0;i<m.getPlanecount();i++) z[i] = 0; m.setall(z); } }

copy アトリビュートは、jit_matrix メソッドの2つの異なったモードを切り替えます。copy が真の場合、入力マトリックスからのデータは、frommatrix メソッドによって 内部の JitterMatrix にコピーされます。copy が真でない場合、JitterMatrix jm は入力マトリックスのピア(等価物)として作られます。どちらのケースも、JitterMatrix は setall メソッドによってゼロにされ、出力されます。

toggle ボックスで copy アトリビュートのオン、オフを切り替えて下さい。左側の jit.pwindow では何が起こるでしょう?

copy アトリビュートがオフで、入力されるマトリックス名と結びつけられた新しい JitterMatrix が作られた場合、このマトリックスのデータに対して直接操作を行ないます。したがって、他のオブジェクトがこのマトリックスに接する前に、マトリックスの内容を変更してしまう可能性があります。このサンプルパッチでは、copy アトリビュートが真の場合、左側の jit.pwindowjit.noise によって作られたマトリックスを正確に表示します。copy アトリビュートが偽の場合には、両方の jit.pwindow オブジェクトが黒になります。これは、mxjクラスがマトリックスのデータを直接変更してしまうためです。従って、マトリックス上で操作をしている場合には、ここで frommatrix によって行なっているように、入力データを確実に内部のキャッシュにコピーしなければなりません。

オブジェクトによる構成

このセクションでは、1つの Java クラスでの、複数の JitterObject の使用について調べてみましょう。

qmetro をオフに切り替え、サブパッチウィンドウを閉じて下さい。composition サブパッチャーを開いて下さい。qmetro をオンに切り替えて下さい。


Java 内で実行される Jitter 処理チェイン

j51composition クラスのコードを調べてみましょう。

import com.cycling74.max.*; import com.cycling74.jitter.*; public class j51composition extends MaxObject { JitterMatrix jm = new JitterMatrix(); JitterMatrix temp = new JitterMatrix(); JitterObject brcosa; JitterObject sobel; boolean brcosafirst = false; j51composition() { declareAttribute("brcosafirst"); brcosa = new JitterObject("jit.brcosa"); brcosa.setAttr("brightness", 2.0f); sobel = new JitterObject("jit.sobel"); sobel.setAttr("thresh", 0.5f); } public void jit_matrix(String mname) { jm.frommatrix(mname); temp.setinfo(jm); if (brcosafirst) { brcosa.matrixcalc(jm, temp); sobel.matrixcalc(temp, jm); } else { sobel.matrixcalc(jm, temp); brcosa.matrixcalc(temp, jm); } outlet(0, "jit_matrix", jm.getName()); } public void notifyDeleted() { brcosa.freePeer(); sobel.freePeer(); } }

クラスのコンストラクタでは、2つの JitterObject が生成されています。brocosajit.brcosa オブジェクトのピア(等価物)をコントロールし、sobel は jit.sobelオブジェクトのピア(等価物)をコントロールします。jit_matrix メソッドは入力マトリックスから jm へデータをコピーし、setinfo メソッドによって temp マトリックスのディメンション(dim)、プレーン数(planecount)、型(type)に jmの値をセットします。これ以後、2つの JitterObject は任意の順序でデータを処理するように使うことができます。アトリビュート brcosafirst が真の場合、最初にbrcosa が、その後 sobel が処理を行ないます。当然、 brcosafirst が偽の場合にはその逆になります。マトリックス内のデータ上での処理は、JitterObject の matrixcalc メソッドの呼出しによって行ないます。2つのアーギュメントは入力、および出力のためのマトリックスを表します。ピア(peer) JitterObject が複数のマトリックスの入力や出力をサポートする場合には、入力や出力にマトリックスの配列を使うこともできます。このシンプルな例は、どのようにしたら Java クラスの中で Jitter オブジェクトによる様々なネットワークを定義することが可能になるかを示すものです。

brcosafirst アトリビュートのオン/オフを切り替えて、結果として表示される映像の違いを見て下さい。

最後に、次の点に注意して下さい。mxjオブジェクトが削除され、notifyDelete メソッドが呼び出された時、ピア Jitter オブジェクトは freePeer() メソッドの呼出しによって解放されます。この呼出しはインスタンス化していた JitterObject それぞれに対して行ないます。freePeer() が呼び出されない場合、C オブジェクトピアは Java のメモリマネージャがガベージコレクション(訳注:使用していないメモリ領域を、メモリマネージャが探索し、解放することです)を行なうまで存在し続けてしまいます。これにはしばらく時間がかかります。

リスニング(待ち受け)

qmetro をオフに切り替え、サブパッチを閉じて下さい。cubiccuver サブパッチを開いて下さい。verbose と表示された toggle ボックスをオンにし、jit.pwindow オブジェクトの中でマウスを動かして、Max ウィンドウの中に表示される結果を観察して下さい。

このセクションは、すでにチュートリアル47:「JavaScriptでのJitterオブジェクト・コールバックの使用」に目を通していることを前提として書かれています。このチュートリアルでは、JavaScript でのオブジェクト・コールバックに関して述べられています。Jitter Java API は、名前を持つ Jitter オブジェクトによって生成されるイベントを「リスニング」するために、同じメカニズムを提供します。このサブパッチャーでは、このメカニズムを使って、名前を持つ jit.pwindow の中のマウスの動きを追跡します。

j51pwindowlistener クラスのソースコードを調べてみましょう。

import com.cycling74.max.*; import com.cycling74.jitter.*; public class j51pwindowlistener extends MaxObject implements JitterNotifiable { JitterListener listener; boolean verbose = false; j51pwindowlistener() { declareIO(1,2); declareAttribute("verbose"); } public void name(String s) { listener = new JitterListener(s,this); } public void notify(JitterEvent e) { //リスニング・コンテキストの名前を取得します String subjectname = e.getSubjectName(); //イベントのタイプ(mouse、mouseidle 等)を取得します String eventname = e.getEventName(); //イベントのアーギュメントを取得します Atom args[] = e.getArgs(); if (verbose) { outlet(1,subjectname); outlet(1,eventname,args); } if ((eventname.equals("mouse"))                ||(eventname.equals("mouseidle"))) { int xy[] = new int[] {args[0].toInt(),args[1].toInt()}; //マウスのx 、y 座標を出力します outlet(0, xy); } } }

最初に注目すべきことは、クラスが JitterNotifiable というインターフェイスを実装(implement)していることです。このシンプルなインターフェイスは、クラスが、イベントが「待ち受けられている」ときに呼び出される notify メソッドを持つことを保証します。notify メソッドが JitterEvent をアーギュメントとして取ることを覚えておいて下さい。このクラスの notify メソッドはこれらの JitterEvent のメソッドを書き、それを使って関係するデータを抽出します。getSubjectName はリスニング・コンテキストの名前を返します。これは、複数の場所を「リスニング」している場合に役に立ちます。getEventName は、待ち受けられるイベントの名前を返します。そして getArgs はイベントのパラメータの詳細を報告するアーギュメントを返します。verbose アトリビュートが使用可の場合、このクラスの notify メソッドはこれらのデータを2番目のアウトレットから出力し、print オブジェクトがこれを Max ウィンドウに送ります。

notify メソッドは、mouseidle(通常のマウスの動き)と mouse(ボタンを押し下げられている間の動き)を等しいものと見なし、eventname の値のテストを実行し続けます。このどちらかの文字列が合致した場合、ウィンドウ内のマウスの x および y 座標位置を示す最初の2つのアーギュメントがリストとして出力されます。

このクラスに関して最後に注目しておくべきことは、何らかのリスニングが発生する以前に、既存のコンテキストによってリスナーが作られていなければならないということです。このケースでは、クラスのインスタンスが name メッセージを受け取るときにこれが起こります。パッチをアンロックすることによって、jit.pwindow オブジェクトが最初に名前を与えられる方法がわかるでしょう。その後、name メッセージが j51pwindowlistener に送信されます。

訳注:実際のチュートリアルパッチでは、ロック状態でも loadmessオブジェクトを見ることができます。loadmessから送信される name mainstreetメッセージは triggerオブジェクトによって、最初に jit.pwindow に、その後j51pwindowlistener に送られています。

パッチの残りの部分では、jitcubiccurbe サンプルクラスを使って、マウスを追尾する3次曲線をレンダリングしています。3次曲線の制御点は、j51frcdistancer クラスによって計算されています。このレッスンのマテリアルを理解しているかどうかを確認するために、このコードを読み通してみたいのではないでしょうか。

import com.cycling74.max.*; import com.cycling74.jitter.*; import java.util.*; public class j51fracdistancer extends MaxObject{ JitterMatrix ctlmatrix =new JitterMatrix(1, "float32", 8); float ctldata[] = new float[8]; int offset[] = new int[] {0}; float frac = 0.5f; float noise = 0.5f; Random r = new Random(); j51fracdistancer() { declareAttribute("frac"); declareAttribute("noise"); } //新しい x,y 入力 public void list(int args[]) { if (args.length != 2) return; float x = (float)args[0]; float y = (float)args[1]; for (int i=0;i<4;i++) { ctldata[i*2] += (x-ctldata[i*2]) * frac +((float)r.nextGaussian()*noise); ctldata[i*2+1] += (y-ctldata[i*2+1]) * frac +((float)r.nextGaussian()*noise); } ctlmatrix.copyArrayToVector(0,offset, ctldata, 8, 0); outlet(0, "jit_matrix", ctlmatrix.getName()); } public void jit_matrix(String mname) { JitterMatrix jm = new JitterMatrix(mname); if (jm.getDim()[0] != 8) return; if (jm.getPlanecount() != 1) return; if (!jm.getType().equals("float32")) return; ctlmatrix.frommatrix(mname); ctlmatrix.copyVectorToArray(0, offset, ctldata, 8, 0); } }

まとめ

このチュートリアルでは、JitterObject、JitterMatrix、JitterListener クラスを紹介し、mxj クラスと組み合わせてこれらを使うことによりデータを直接操作する方法について、また、処理の図式を定義するために既存の Jitter オブジェクトによる組織を使うことについて見てきました。そして、とりわけ、ユーザインターフェイスを構成する要素の構築を容易に行なえるような方法の中で、イベントを扱うJitter オブジェクトをどのように「リスン(待ち受け)」するかについて見てきました。