マトリックスオペレータ クイックスタート

この章の目的は、ビデオストリーム用に最も一般的に用いられるマトリックス(すなわち、4つのプレーンの char データ)の処理を行なうことができるシンプルなマトリックスオペレータ(MOP)を開発する方法に関して、簡単で高レベルな概要を示すことです。そのために、ここでは SDK サンプル jit.scalebias を使います。

複数のデータ型、プレーン数、ディメンション、入出力などを扱うマトリックスオペレータを作る方法の詳細に関しては次の章で説明しています。この章では、前の章までの Jitter オブジェクトモデルや Max ラッパークラスについてだけでなく、Jiiter チュートリアルで述べられているような Jitter の多次元マトリックス表現や、Max パッチャーからのマトリックスオペレータの使用に関しても熟知していることを前提にしています。

MOP Jitter クラスの定義

Jitte のクラス定義において、私たちは、マトリックスオペレータのためのいくつかの新しいコンセプトを導入します。Jitter オブジェクトモデルの章で説明した標準的なメソッドやアトリビュートの定義に加え、オペレータの入力や出力の数、オペレータが扱うことができる型、プレーン数、次元に関する制限などについて定義する必要があるでしょう。これらのことは、 jit_mop クラスのインスタンスを作り、jit_mop オブジェクトのいくつかのステートを設定して、これををあなたのJitterクラスのアドーンメント(装飾)として追加することによって可能になります。次に示すコードの断片は、SDK サンプル jit.scalebias からの引用です。

// インプット、アウトプットを持つ jit_mop の新しいインスタンスを作る。 mop = jit_object_new(_jit_sym_jit_mop,1,1); // すべてのインプット、アウトプットの型を1種類に強制する。 jit_mop_single_type(mop,_jit_sym_char); // すべてのインプット、アウトプットのプレーン数を一定の値に強制する。 jit_mop_single_planecount(mop,4); // クラスのアドーンメントとしてjit_mop object を追加する。 jit_class_addadornment(_jit_scalebias_class,mop);

アトリビュートインスタンスを作った時と同じように、jit_object_new を使って、あなたの jit_mop インスタンスを作ります。jit_mop コンストラクタはインプット、アウトプット各々のために2つの整数引数を持ちます。デフォルトでは、各々の MOP のインプット、アウトプットにおけるプレーン数、型、ディメンションに関する制限はなく、第1の(左端の)インプットのプレーン数、型、ディメンションにリンクされます。このデフォルトの動作はオーバーライドすることができ、このシンプルな4プレーンの char 型を持つ jit.scalebias の例では、jit_mop_single_type および jit_mop_single_planecount というユーティリティ関数によって、対応する型とプレーン数の制限を強制しています。jit_mop クラスに関するより詳しい情報は、次章の「マトリックスオペレータの詳細」およびJitter APIリファレンスを参照して下さい。

jit_mop を作り、あなたのオブジェクトの必要に従って設定を完了した後、jit_class_addmethod 関数を使って、これをあなたのJitterクラスにアドーンメントとして追加します。アドーンメントはJitterオブジェクトに対し、付加的な情報、および、既存のクラスに追加されたインスタンスの動作を持たせるための1つの方法です。アドーンメントについての詳細は、この章の中で説明します。

さらに、マトリックスオペレータが実行する仕事の大部分である、マトリックスを計算するメソッドを定義する必要があります。これは、プライベート(公開されない)関数としてjit_class_addmethodを使い、型指定のないメソッドとしてシンボル matrix_calc にバインドします。

jit_class_addmethod(_jit_scalebias_class, (method)jit_scalebias_matrix_calc, "matrix_calc", A_CANT, 0L);

Jitter クラスのコンストラクタ/デストラクタ

あなたのマトリックスオペレータのコンストラクタやデストラクタに対しては、標準の初期化や、あらゆる Jitter オブジェクトにおいて必要となるクリーンアップを除き、特別に何かを追加する必要はありません。入出力用のマトリックスは保守され、Max ラッパーの非同期インターフェイスによってのみ必要とされます。Jitter の MOP は入力と出力のためのマトリックスを持ちませんが、すべての入力と出力に同期したマトリックス演算メソッドの呼び出しを前提としています。C 、Java 、JavaScript? のような言語から使用される場合は、マトリックス演算メソッドに渡されるマトリックスの保守と提供はプログラマ次第です。

マトリックス演算メソッド

matrix_calc」メソッドは、マトリックスオペレータの最も重要なメソッドで、この中で最も一般的なマトリックス演算処理が行なわれます。 matrix_calc メソッドは、プライベートで、A_CANT 型指定子を用いて型を持たないメソッドとして定義し、シンボル "matrix_calc" にバインドする必要があります。オブジェクトはこのメソッドの中で、演算に使用する入力マトリックスと出力マトリックスのリストを受け取ります。あなたはこれらのマトリックスをロックし、重要なアトリビュートに関しての問い合わせを行う必要があります。そして、型、プレーン数、あるいはディメンションに関するすべての必要条件が満たされているかどうかをを確認した後、実際にデータを処理し、マトリックスへのアクセスロックを解除してから戻ります。これは次の例のように定義しなければなりません。

t_jit_err jit_scalebias_matrix_calc(t_jit_scalebias *x, void *inputs, void *outputs) { t_jit_err err=JIT_ERR_NONE; long in_savelock,out_savelock; t_jit_matrix_info in_minfo,out_minfo; char *in_bp,*out_bp; long i,dimcount,planecount,dim[JIT_MATRIX_MAX_DIMCOUNT]; void *in_matrix,*out_matrix; // 対応するインプットとアウトプットのリストから、インプットとアウトプット // の0番目のインデックスを取得 in_matrix= jit_object_method(inputs,_jit_sym_getindex,0); out_matrix= jit_object_method(outputs,_jit_sym_getindex,0); // オブジェクト、およびインプットとアウトプット両方のマトリックスが有効ならば // 処理を行い、そうでない場合はエラーを返す。 if (x&&in_matrix&&out_matrix) { // インプットとアウトプットのマトリックスをロック。 in_savelock = (long) jit_object_method(in_matrix,_jit_sym_lock,1); out_savelock = (long) jit_object_method(out_matrix,_jit_sym_lock,1); // インプットとアウトプットのためのマトリックス情報構造体に書き込む。 jit_object_method(in_matrix,_jit_sym_getinfo,&in_minfo); jit_object_method(out_matrix,_jit_sym_getinfo,&out_minfo); // マトリックスのデータポインタを取得 jit_object_method(in_matrix,_jit_sym_getdata,&in_bp); jit_object_method(out_matrix,_jit_sym_getdata,&out_bp); // データポインタが無効な場合は、エラーをセットし、クリーンアップを行う if (!in_bp) { err=JIT_ERR_INVALID_INPUT; goto out;} if (!out_bp) { err=JIT_ERR_INVALID_OUTPUT; goto out;} // 互換性を持つ型を強制する。 if ((in_minfo.type!=_jit_sym_char) || (in_minfo.type!=out_minfo.type)) { err=JIT_ERR_MISMATCH_TYPE; goto out; } // 互換性を持つプレーン数を強制する。 if ((in_minfo.planecount!=4) || (out_minfo.planecount!=4)) { err=JIT_ERR_MISMATCH_PLANE; goto out; } // ディメンション/プレーン数を取得 dimcount= out_minfo.dimcount; planecount = out_minfo.planecount; for (i=0;i<dimcount;i++) { // インプットとアウトプットのサイズが異なる場合は // 2つのマトリックスの交差(共通部分)を使用 dim[i] = MIN(in_minfo.dim[i],out_minfo.dim[i]); } // マルチプロセッサが使用できる場合、マルチスレッドで calculate_ndim 関数を // 呼び出すために、パラレルユーティリティ関数を使用して演算。 jit_parallel_ndim_simplecalc2( (method)jit_scalebias_calculate_ndim, x, dimcount, dim, planecount, &in_minfo, in_bp, &out_minfo, out_bp, 0 /* フラグ 1 */, 0 /* フラグ 2 */); } else { return JIT_ERR_INVALID_PTR; } out: // マトリックスのロックステートを以前の値に復元 jit_object_method(out_matrix,_jit_sym_lock,out_savelock); jit_object_method(in_matrix,_jit_sym_lock,in_savelock); return err; }

N次元マトリックスの処理

Jitter は N次元マトリックス(Nは1から32まで)の処理をサポートするため、ほとんどのマトリックスオペレータは再帰的関数で設計され、これを使ってより低い次元(通常2次元のデータが最も多く使用されます)のデータのスライスを処理します。このような再帰的関数は、通常、myobject_calculate_ndimという名前を付け、あなたの matric_calc メソッドから、あるいはパラレル処理関数の1つを通して呼び出します。パラレル処理関数については、後の章で説明します。

固定小数点やポインタの演算に関する詳細なチュートリアルの提供は、このドキュメンテーションの範囲を超えていますが、次の例ではこれらが使用されています。コードはマトリックスデータを通してポインタをインクリメントし、個々のマトリックスセルの平面の要素(プレーナーエレメント)に係数を掛け、バイアス量を加算することによって、スケーリングを行っています。この処理は固定小数点演算(8ビット小数コンポーネントを使用)で行われます。これは、整数から浮動小数点データへの変換、および逆変換処理が負荷の大きい操作になるためです。

jit.scalebiasオブジェクトは2つのモードを持っており、1つはプレーンを合計して、もう1つは各プレーンを独立したものとして処理します。セルごとに処理を行うより、行ごとに扱う方がパフォーマンスを向上させることができ、マトリックスごとに行うより、行ごとに行う方がいくぶんコードの量を減らすことができます。マトリックスベースで扱うことによって、僅かにパフォーマンスを向上させることができますが、通常、行ごとの処理が、最適なトレードオフを行うための適切なポイントになります。

// 2D セクションを一度に扱うことによってより高い次元のマトリックスを扱うための再帰的関数 void jit_scalebias_calculate_ndim(t_jit_scalebias *x, long dimcount, long *dim, long planecount, t_jit_matrix_info *in_minfo, char *bip, t_jit_matrix_info *out_minfo, char *bop) { long i,j,width,height; uchar *ip,*op; long ascale,rscale,gscale,bscale; long abias,rbias,gbias,bbias,sumbias; long tmp; if (dimcount<1) return; // 安全のため switch(dimcount) { case 1: // 1D しか持たない場合、2D として解釈し、2D のケースに進みます。 dim[1]=1; case 2: // 浮動小数点のスケール係数を、固定小数点の int にコンバート ascale = x->ascale*256.; rscale = x->rscale*256.; gscale = x->gscale*256.; bscale = x->bscale*256.; //浮動小数点のバイアス値を、固定小数点の int にコンバート abias= x->abias*256.; rbias= x->rbias*256.; gbias= x->gbias*256.; bbias= x->bbias*256.; // sum モード (1) の効率化のために、1つのバイアス値を作ります。 sumbias = (x->abias+x->rbias+x->gbias+x->bbias)*256.; width= dim[0]; height = dim[1]; // 個々の列 for (i=0;i<height;i++) { // バイトストライド(byte stride)に従ってデータポインタをインクリメント ip = bip + i*in_minfo->dimstride[1]; op = bop + i*out_minfo->dimstride[1]; switch (x->mode) { case 1: // 合計し、0-255 の範囲に収まるように固定して、すべてのアウトプット用 // プレーンにセットします。 for (j=0;j<width;j++) { tmp= (long)(*ip++)*ascale; tmp += (long)(*ip++)*rscale; tmp += (long)(*ip++)*gscale; tmp += (long)(*ip++)*bscale; tmp= (tmp>>8L) + sumbias; tmp= (tmp>255)?255:((tmp<0)?0:tmp); *op++ = tmp; *op++ = tmp; *op++ = tmp; *op++ = tmp; } break; default: // 0-255 の範囲への固定はプレーン毎個別に適用されます。 for (j=0;j<width;j++) { tmp = (((long)(*ip++)*ascale)>>8L)+abias; *op++ = (tmp>255)?255:((tmp<0)?0:tmp); tmp = (((long)(*ip++)*rscale)>>8L)+rbias; *op++ = (tmp>255)?255:((tmp<0)?0:tmp); tmp = (((long)(*ip++)*gscale)>>8L)+gbias; *op++ = (tmp>255)?255:((tmp<0)?0:tmp); tmp = (((long)(*ip++)*bscale)>>8L)+bbias; *op++ = (tmp>255)?255:((tmp<0)?0:tmp); } break; } } break; default: // 2D より大きい次元の処理を行う場合、個々のより低い次元のスライスのために // ベースポインタをセットし、デクリメントされた dimcount と新しいベース // ポインタによって、この関数の再帰的な呼出しを行います。 for(i=0;i<dim[dimcount-1];i++) { ip = bip + i*in_minfo->dimstride[dimcount-1]; op = bop + i*out_minfo->dimstride[dimcount-1]; jit_scalebias_calculate_ndim(x,dimcount1, dim,planecount,in_minfo,ip,out_minfo,op); } } }

Jitter マトリックスは、多次元配列を使うのではなく、1次元の配列の中にパックされ、個々の次元にバイトストライド(byte strides)を定義することによって大きな柔軟性を持たせています。これにより、マトリックスが、より大きいマトリックスのサブリージョン(部分領域)を参照することを可能にすると同時に、緊密にパックされていないデータもサポートします。したがって、このコードでは、マトリックスの各々のプレーンの中の個々ののセルへのアクセスには、多次元配列のシンタックス(構文)を使うのではなくポインタ演算を使っており、各々の次元の繰り返し処理では、対応するバイトストライド(バイト間隔)がベースポインタに加算されます。このバイトストライドは、t_jit_matrix_info 構造体の dimstride エントリに格納されています。

Jitter では、セル内部のプレーン、及び最初の次元(dim[0])の中のセルは緊密にパックされていることが必要である点に注意して下さい。上記のコードは、これが真実であることを前提に書かれているため、dim[0]においてはバイトストライドを調べるのではなく、単なるポインタのインクリメントを使っています。

MOP Max ラッパークラスの定義

Max パッチャーの中で MOP クラスを使用するためには、Max ラッパークラスを作る必要があります。あらゆる Jitter クラスのラップに使われる標準のメソッドに加え、MOP では特別なメソッドと情報をMax に追加する必要があります。その1つは、Max ラッパークラスでは、左端のインプット以外の、マトリックスインプット、アウトプットそれぞれのための jit.matrix のインスタンスのメモリ割当て、およびメンテナンスを行い、Max の非同期イベントモデルに対応する必要があるということです。このメンテナンスを実行するために、Max ラッパークラスは、特別なメソッドとアトリビュートを持たなければなりません。これは、型、プレーン数、ディメンション、適応性、そして、内部のマトリックスのための名前による参照をセットするものです。これらのメッセージはすべて Max ラッパーによる実装専用で、C、Java、Javascript によるマトリックスオペレータの使用の中で使うことはできません。マトリックスアウトプットモードや jit_matrix メッセージ、bang メッセージのためには、共通のメソッドとアトリビュートもありますが、これらはすべて MOP の Max ラッパーに固有のものです。

これらの特別なアトリビュートとメソッドはmax_jit_classex_mop_wrap 関数によって追加されます。この関数は、あなたの Max エクスターナルの main 関数の中で max_jit_classex_setupおよび、jit_class_findbynameの呼出しの後、max_jit_classex_standard_wrapの呼出しの前に、呼び出す必要があります。いくつかのデフォルトのメソッドとアトリビュートは、様々なフラグを利用してオーバーライドでき、これはmax_jit_classex_mop_wrapの引数 flags と結びつけることができます。これらのフラグのリストは次のようになりますが、ほとんどのシンプルな MOP では必要ではありません。

#define MAX_JIT_MOP_FLAGS_OWN_ALL 0xFFFFFFFF #define MAX_JIT_MOP_FLAGS_OWN_JIT_MATRIX 0x00000001 #define MAX_JIT_MOP_FLAGS_OWN_BANG 0x00000002 #define MAX_JIT_MOP_FLAGS_OWN_OUTPUTMATRIX 0x00000004 #define MAX_JIT_MOP_FLAGS_OWN_NAME 0x00000008 #define MAX_JIT_MOP_FLAGS_OWN_TYPE 0x00000010 #define MAX_JIT_MOP_FLAGS_OWN_DIM 0x00000020 #define MAX_JIT_MOP_FLAGS_OWN_PLANECOUNT 0x00000040 #define MAX_JIT_MOP_FLAGS_OWN_CLEAR 0x00000080 #define MAX_JIT_MOP_FLAGS_OWN_NOTIFY 0x00000100 #define MAX_JIT_MOP_FLAGS_OWN_ADAPT 0x00000200 #define MAX_JIT_MOP_FLAGS_OWN_OUTPUTMODE 0x00000400

Max クラス コンストラクタ/デストラクタ

あなたの Max クラスのコンストラクタの中で、MOP のインプットとアウトプットのために必要なマトリックス、対応するマトリックスインレット、アウトレット、マトリックス処理アーギュメント、および他の MOP セットアップへのメモリ割当てを行う必要があります。 max_jit_mop_setup_simple 関数はこれらの関数や、 Jitter インスタンスをラッピングするために必要なその他のタスクを管理します。 この関数を使用することによって、max_jit_mop_setup_simple と互換性のない特別な動作を求められるような場合を除くシンプルなケースでは、あなたの Jitter クラスのラッピングは更に簡素化されます。 次は、jit.scalebiasオブジェクトの Max クラスためのコンストラクタです。

void *max_jit_scalebias_new(t_symbol *s, long argc, t_atom *argv) { t_max_jit_scalebias *x; void *o; if (x = (t_max_jit_scalebias *) max_jit_obex_new( max_jit_scalebias_class,gensym("jit_scalebias"))) { // Jitter オブジェクトのインスタンス化 if (o=jit_object_new(gensym("jit_scalebias"))) { // 標準の MOP Max ラッパーセットアップを行うタスクの取り扱い max_jit_mop_setup_simple(x,o,argc,argv); // アトリビュートアーギュメント処理 max_jit_attr_args(x,argc,argv); } else { error("jit.scalebias: could not allocate object"); freeobject(x); } } return (x); }

これは、あなたのために管理を行ってくれる max_jit_mop_setup_simple 関数の短いリストの例です。オブジェクトが特別な要求を持っている場合、必要に応じて次の関数のサブセットを使用することができます。

t_jit_err max_jit_mop_setup_simple(void *x, void *o, long argc, t_atom *argv) { max_jit_obex_jitob_set(x,o); max_jit_obex_dumpout_set(x,outlet_new(x,NULL)); max_jit_mop_setup(x); max_jit_mop_inputs(x); max_jit_mop_outputs(x); max_jit_mop_matrix_args(x,argc,argv); return JIT_ERR_NONE; }

Max クラスのデストラクタでは、MOP のために割当てられたリソースを解放する必要があります。これはmax_jit_mop_free関数によって行うことができます。この関数は、内部の Jitter インスタンス、および Max クラスの obex データを解放する前に呼び出さなければなりません。実例として、jit.scalebiasのデストラクタのリストを次に示します。

void max_jit_scalebias_free(t_max_jit_scalebias *x) { // Max ラッパーリソースの解放 max_jit_mop_free(x); // 内部の Jitter オブジェクトのインスタンスを探し、解放 jit_object_free(max_jit_obex_jitob_get(x)); // obex エントリに関連づけられたリソースの解放 max_jit_obex_free(x); }