Max 5 API Reference

マトリックスオペレータの詳細

この章の目的は、マトリックスオペレータがどのようなもので、どのように動作するのかについての詳細を説明することです

Jitter のマトリックスデータは、一般的に、そのデータが何を意味しているかを考慮しない生データであると考えられます。これにより、特別な情報を必要とせずに、様々な種類のデータに対してシンプルな基本的操作を適用することが可能になっています。そのため、ほとんどの MOP は汎用として使用されます。前の章で示した jit.scalebias の例は、その用語や型、プレーン数の制限などから、ビデオに特化したものと考えられます。しかし、基本的に、これは入力されるマトリックスの各プレーンに対して、積と和の演算を行っているにすぎません。 この章では、MOP のインプットとアウトプットの設定の方法、インプット、アウトプットに対するアトリビュートの制限やリンクについての詳細を述べ、matrix_calc メソッドの中で処理しなければならない内容について、また、必要に応じてデフォルトの動作をオーバーライドし、MOP を Max 環境から見えるようにする方法について述べます。

MOP Jitter クラスの定義

「マトリックスオペレータ クイックスタート」で述べたように、 jit_object_new() 関数によって MOP のためにjit_mop のインスタンスを作り、 jit_class_addadornment() 関数によって、それをあなたの Jitter クラスにアドーンメントとして追加しなければなりません。jit_mop オブジェクトは、次のような情報を保持しています。それは、オブジェクトがインプットとアウトプットをいくつ持つか、どのような型、プレーン数、ディメンションをサポートするか、インプットが入力されるマトリックスに対してどのように反応しなければならないかということです。この情報は、MOP Max ラッパークラスの場合と同様、実際にインプットとアウトプットのために追加されるマトリックスを保守する Jitter オブジェクトのラッパーにだけ関連します。C、Java、JavaScript から使われる場合、MOP によって強制される制限に従うようにマトリックスを渡すことは、プログラマの責任になります。次の例は、jit_mop オブジェクトをインスタンス化して、追加するものです。

   // 1インプット、1アウトプットを持つ jit_mop の新しいインスタンスの生成 
   mop = jit_object_new(_jit_sym_jit_mop,1,1);
   
   // jit_mop オブジェクトを、アドーンメントとしてクラスに追加
   jit_class_addadornment(_jit_your_class,mop);

 

jit_mop_io オブジェクト

jit_mop のそれぞれのインスタンスは、いくつかのインプットとアウトプットを持っています。これは、コンストラクタに渡されるインプットとアウトプットの数を表す引数によって指定されたものです。これらのインプット、アウトプットそれぞれに対して、インプットやアウトプットに特有の情報を記録するための、 jit_mop_io のインスタンスが存在します。この情報には、型、プレーン数、ディメンションの制限などがあります。次のように、getinput や getoutput メソッドを、整数値によるインデックスを引数として呼び出すことによって、インプット、またはアウトプットオブジェクトにアクセスすることができます。

   input = jit_object_method(mop,_jit_sym_getinput,1);
   output = jit_object_method(mop,_jit_sym_getoutput,1);

 

一度これらのインプット、またはアウトプットへの参照を取得すると、jit_mop_io attributes に対して、値の問い合わせや設定を行うことができるようになります。アトリビュートは、一般的に次のように設定されます。

types :許可される型のシンボルのリスト、これらのうちの最初のものがデフォルトになります。
mindim および maxdim:許可される個々のディメンションのサイズの最小値と最大値。
mindimcount および maxdimcount:許可されるディメンションの数の最小値と最大値。
minplanecount および maxplanecount:許可されるプレーンの数の最小値と最大値。
typelink:I/0 が左端の入力マトリックスに合わせて、その型を変えなければならないかどうかを決定するフラグ。
dimlink:I/0 が左端の入力マトリックスに合わせて、そのディメンションを変えなければならないかどうかを決定するフラグ。
planelink:I/0 が左端の入力マトリックスに合わせて、そのプレーン数を変えなければならないかどうかを決定するフラグ。

インプット/アウトプットアトリビュートの制限

デフォルトでは、すべての型、ディメンション、プレーン数が許可され、リンクが有効(イネーブル)になっています。あなたの MOP に対して特定の制限を設けたい場合や、特別なインプットやアウトプットに対してデフォルトと異なるリンク動作を持たせたい場合、対応するアトリビュートを設定することができます。例えば、プレーン数を常に4つのプレーンにセットしたい場合、次のように、minplanecountおよびmaxplanecountアトリビュートを4にセットします。

   output = jit_object_method(mop,_jit_sym_getoutput,1);
   jit_attr_setlong(output,_jit_sym_minplanecount,4);
   jit_attr_setlong(output,_jit_sym_maxplanecount,4);

 

jit.scalebias の例では、内部的にこれらのアトリビュートを設定するためのユーティリティ関数 jit_mop_single_planecount() を呼び出すのではなく、minplanecount と maxplanecount アトリビュートを使って プレーン数(planecount) を設定しています。同様なことは、型やディメンションを制限するために行うこともできます。リンクに関しては、jit.convolve の場合のように、右からのインプットを最も左のインプットのサイズに適応させたくないならば、次のように dimlink アトリビュートをオフにすれば良いでしょう。

   input2 = jit_object_method(mop,_jit_sym_getinput,2);
   jit_attr_setlong(input2,_jit_sym_dimlink,0);

 

同様なことは、型やプレーン数のリンクを取り除く場合にも行うことができます。 jit_mop_input_nolink()jit_mop_output_nolink() というユーティリティ関数は、これらのリンクに関するアトリビュートを false(ゼロ)に設定します。

ioproc 関数

右側のマトリックス入力の場合、通常、入力されたデータは MOP Maxラッパークラスによってコピーされます。入力されるマトリックスを MOP Maxラッパークラスで受け取ると、ioproc という関数が呼び出され、デフォルトの ioproc が現在のアトリビュート(これは左側のインプットとリンクされているかも知れません)を使ってデータをコピーします。デフォルトの ioproc は、下の jit_mop_ioproc_copy_adapt() 関数の中で挙げられているようなシグネチャを渡される関数を伴った ioproc メソッドの呼出しによってオーバーライドすることができます。jit_mop_ioproc_copy_adapt() 関数は、何らかの制限との衝突を起こさない限り、常にインレットに入力されるマトリックスのアトリビュートに適応します。jit.concat 用の SDK プロジェクトは、この jit_mop_ioproc_copy_adapt() 関数の使用法を示しています。

 

t_jit_err jit_mop_ioproc_copy_adapt(void *mop, void *mop_io, void *matrix)
{
   void *m; // 転送先(デスティネーション)のマトリックス
   t_jit_matrix_info info;
   
   // mop_io から転送先のマトリックスを調べる
   if (matrix&&(m=jit_object_method(mop_io,_jit_sym_getmatrix))) 
   {
      // 入力されるマトリックスの情報を取得
      jit_object_method(matrix,_jit_sym_getinfo,&info);

      // mop_io アトリビュートに基づくマトリックス情報の制限
      jit_object_method(mop_io,_jit_sym_restrict_type,&info);
      jit_object_method(mop_io,_jit_sym_restrict_dim,&info);
      jit_object_method(mop_io,_jit_sym_restrict_planecount,&info);

      // 転送先マトリックスの情報をセット
      jit_object_method(m,_jit_sym_setinfo,&info);

      // frommatrix メソッドでデータをコピー
      jit_object_method(m,_jit_sym_frommatrix,matrix,NULL);
   }

   return JIT_ERR_NONE;
}

 

可変のインプット/アウトプット

あなたの jit_mop を作る際に、インプットやアウトプットに負の引数を与えることによって、可変インプット/アウトプット MOP の指定を行うことができます。可変のインプットやアウトプットを使う場合、あなたのクラス定義の中には、個々のインプット、アウトプットのための jit_mop_io が存在しないため、type、dim、planecount、リンク用アトリビュートをセットすることができません。そのため、何かデフォルトの動作が必要な場合は、これを他の方法によって行わなければなりません。例えば、MOP Max ラッパークラスの jit_matrix メソッドをオーバーライドする、あるいは、MOP ラッパークラスの標準の jit_matrix メソッドの中から呼び出される mproc の定義を行うという方法です。SDK の中の、jit.pack、jit.unpack、 jit.scissors、 jit.glue オブジェクトは可変のインプットとアウトプットを持ったMOP の例です。jit_matrix、mproc および MOP Max ラッパークラスの他のデフォルトのメソッドのオーバーライドに関するより詳しい情報は、この章の後の方で述べられています。

クラスアドーンメントとしての jit_mop の追加

jit_mop オブジェクトのすべてのインプットとアウトプットの設定を終えた後、jit_mop オブジェクトを、 jit_class_addadornment() 関数によってあなたの Jitter クラスに追加しなければなりません。アドーンメントは jitter クラスからの問い合わせを受けることができます。これは、下に示すような、Jitter クラスのポインタとアドーンメントオブジェクトのクラス名を渡された jit_class_adornment_get() の呼出しによっていつでも行うことができます。

   // add jit_mop object as an adornment to the class
   jit_class_addadornment(_jit_your_class,mop);

   // look up jit_mop adornment 
   mop = jit_class_adornment_get(_jit_your_class,_jit_sym_jit_mop);

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

MOP Jitter クラスのエントリポイントは matrix_calc メソッドです。このメソッドは、インプットのためのマトリックスのリストとアウトプットのためのマトリックスのリストを渡されます。マトリックスのコピーや、マトリックスの適応を行う動作は、matrix_calc の仕事ではありません。このメソッドは、単にマトリックスが有効で、互換性があることを確認し、そうであるならばこれを処理するだけです。ある種のオブジェクトは、アウトプットマトリックスの dim(ディメンション)、type(データ型)、planecount(プレーン数)を修正する場合があります(SDK プロジェクトの jit.thin がその例にあたります)。しかし、コピーや、jit_mop_io オブジェクトで定義された MOP/IO の制限に対する適応を実行することは、呼出し側(すなわち、matrix_calc メソッドを呼び出す Max ラッパークラス、または C、Java、Javascript コード)の責任になります。

インプットとアウトプットのリストへのアクセス

あなたの matrix_calc メソッドに引数として渡されるインプットとアウトプットのリストは Jitter オブジェクトです。また、それぞれのインプットとアウトプットへのポインタは、整数による引数とともに getindex メソッドを呼び出すことによって取得されます。引数には0から始まるリストのインデックスを指定します。この戻り値が NULL でないことを確認しておかなければなりません。次はその例です。

   // 該当するインプットとアウトプットのリストから、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) 
   {
      // ... データの処理 ...

   } else {
      return JIT_ERR_INVALID_PTR;
   }

jit_matrix は、jit_matrix のインスタンスを返すgetindex メソッドを持っているため、技術的には、インプットとアウトプットのリストを渡す代わりに、jit_matrix のインスタンスを引数として渡すことも可能です。これは、ダイナミックバインディング(動的バインディング)の例にあたります。matrix_calc メソッドの内部の、もう1つのダイナミックバインディングの例は、リストの要素が jit_matrix のインスタンスではなく、jit_mop_io のインスタンスである場合です。この場合、jit_mop_io オブジェクトは jit_matrix を「装飾する」クラスであるため、Jitter がダイナミックバインディングを使用することによって、すべての対応するメソッドは、jit_mop_io によって参照される jit_matrix に渡されます。事実、jit_matrix の標準インターフェイスに対応するあらゆる Jitter オブジェクトは、インプットまたはアウトプットとして渡すことが可能です。このことが理解しづらいのであれば、根底となっている実装についてあまり深く考えず、単に jit_matrix のインスタンスが渡されているとみなすことができます。結局のところ、同じような動作をするのであれば、異なったものでも構わないということです。

マトリックスのロックとアンロック

マトリックスに対する処理を行なう場合、マトリックスのデータやアトリビュートが処理の最中に変更されないように、事前にマトリックスを「ロック」しておく必要があります。マトリックスのロックは、jit_matrix インスタンスの lock メソッド に整数の引数 1 (真)を与えて呼び出すことで実行できます。処理が終わった後で元に戻すために、現在のロックのステート(状態)を保存しておかなければなりません。ロック処理は、マトリックスオブジェクトが NULL でないことを確認した後、最初に行なわなければならないことです。次はその例です。

   // インプット、アウトプットのマトリックスをロック   
   in_savelock = (long) jit_object_method(in_matrix,_jit_sym_lock,1);
   out_savelock = (long) jit_object_method(out_matrix,_jit_sym_lock,1);

   // ... データの処理... 

out:
   // マトリックスのロックステートを以前の値に復元 
   jit_object_method(out_matrix,_jit_sym_lock,out_savelock);
   jit_object_method(in_matrix,_jit_sym_lock,in_savelock);

マトリックス情報の取得

マトリックスをロックしたことにより、マトリックスに関する情報を取得する準備ができたことになります。情報の取得は、 t_jit_matrix_info 構造体のインスタンスへのポインタを伴う getinfo メソッド の呼び出しによって行われます。この、 t_jit_matrix_info 構造体は、マトリックスに共通した様々なアトリビュートと、マトリックスデータのデータ構成を保持していますが、これらの情報を一回の呼出しによって得る方法は、個々のアトリビュートに対する問い合わせを行うよりも便利です。この情報は、通常、matrix_calc メソッドの実行に必要な条件との互換性を確認する目的でテストされます(このメソッドは、C、Java、Javascript から呼び出されるかもしれないため、MOP Max ラッパーがすでにこれらの条件を強制していることを前提とすることはできません)。また、型、プレーン数、ディメンションに基づいた、適切なポインタ演算を行うためにも使用されます。さらに、より高次のディメンションは緊密にパックされていないため、ディメンション間のバイトストライドに基づく適切なポインタ演算を行うためにも使用されます。 t_jit_matrix_info 構造体の内部は次のようになっています。

typedef struct _jit_matrix_info
{
   long      size;         							// バイト単位でのサイズ (0xFFFFFFFF=UNKNOWN)
   t_symbol   *type;         						// 基本となるデータ型
   long      flags;         						// マトリックスフラグ: my data? handle?
   long      dimcount;      						// ディメンションの数
   long      dim[JIT_MATRIX_MAX_DIMCOUNT]; 			// 各ディメンションのサイズ      
   long      dimstride[JIT_MATRIX_MAX_DIMCOUNT]; 	// バイト単位
   long      planecount;      						// プレーン数
} t_jit_matrix_info;

次は t_jit_matrix_info 構造体に書き込むための getinfo メソッドの呼出し例です。

   // インプットとアウトプットの t_jit_matrix_info 構造体に情報を格納    
   jit_object_method(in_matrix,_jit_sym_getinfo,&in_minfo);
   jit_object_method(out_matrix,_jit_sym_getinfo,&out_minfo);

データポインタの取得

t_jit_matrix_info 構造体はメタ・データですが、実際のマトリックスデータへはデータポインタを取得することによってアクセスできます。これは、マトリックスの getdata メソッドにポインタへのポインタを渡して呼び出すことによって行います。このポインタはどのような型でも可能ですが、あなたのマトリックスのデータ型や dimstride(ディメンションのバイトストライド) に基づいてバイト幅によるポインタ演算を行う必要があるため、通常 char(または byte )型のポインタになります。このポインタが有効なものであるかどうかを、データを処理する前に確認しておくことは重要です。これは次に示すように行います。

   // マトリックスデータポインタの取得
   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;}

データの処理

データ処理のコードを matrix_calc の内部に組み込むことは可能ですが、通常、再帰によって N次元処理を行うための別のルーチンを利用します。このルーチンは潜在的にマルチプロセッサに処理を割り当てる能力を持つものです。N次元再帰処理関数(通常、myobject_calculate_ndim という名前をつけます)については、次のセクションで述べています。この calculate_ndim 関数に対して、あなたのオブジェクトへのポインタ、演算で使用する全体のディメンション数、ディメンションサイズ、プレーン数を、必要なマトリックス情報構造体や、個々のインプット、アウトプットへのデータポインタと一緒に渡さなければなりません。次のコードで示すように、このメソッドを直接呼び出すことが可能です。

   // カレントのスレッドで calculate_ndim 関数を直接呼び出す
   jit_scalebias_calculate_ndim(x, dimcount, dim, planecount, 
      &in_minfo, in_bp, &out_minfo, out_bp);

また、このメソッドを Jitter 1.5 で提供されている並列処理ユーティリティ関数を使って呼び出すこともできます。これにより、マルチプロセッサが利用可能な場合には、大きなマトリックスの処理を自動的にマルチプロセッサに対して横断的に送ることができます。次の図は、並列処理ユーティリティのデータ転送と演算を示しています。

parallel-processing.jpg

並列処理は、マトリックスを小さなマトリックスに分解し、それぞれがオリジナルのインプットとアウトプットの部分領域を参照することによって行われます。新しいオブジェクトは作られず、単に t_jit_matrix_info 構造体とオフセットデータポインタを追加しています。jitter 1.5 はこの目的のために処理スレッドのプールを保守します。これによって、スレッドを生成するオーバーヘッドは生じず、スレッド同期のための小さなオーバーヘッドが生じるだけで済みます。Jitter 1.5 は、この同期によるオーバーヘッド差し引いても見合うような数多くのデータがある場合にのみ、マルチスレッドへのデータの振り分けを行います。

あなたのオブジェクトがある種の空間的操作(コンボリューション - 畳み込み演算、ローテーション - 回転)、スケーリング - 拡大縮小など)を行なう場合、並列処理ユーティリティを使用するマトリックスをあらかじめ分割しておくか、あるいは、並列処理の使用を避けて、現在のスレッドから直接呼び出す必要があります。これは注意しておくべき重要な点です。 jit.scalebias の例では、一度に1つのピクセルを処理する(すなわち、点ごとの操作を行なう)だけなので、本質的に並列処理が可能です。そのため、この例では下に示すように、マルチプロセッサを利用しています。

   // 演算:マルチプロセッサが実装されている場合、マルチスレッドでclculate_ndim 関数を呼び出すた
   // めに、並列処理ユーティリティ関数を使用しています。  
   jit_parallel_ndim_simplecalc2(
      (method)jit_scalebias_calculate_ndim,
      x, dimcount, dim, planecount, 
      &in_minfo, in_bp, &out_minfo, out_bp,
      0, 0 );

重要な注意:あなたのオブジェクトが点ごとの処理を行なうものかどうかわからない場合、あるいは、アルゴリズムを並列に処理する方法を完全に理解していない場合、並列処理ユーティリティを使用すべきではありません。この場合、単純に、関数を直接呼び出さなければなりません。

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

「マトリックスオペレータ・クイックスタート」の章では、 jit.scalebias オブジェクトを例にとり、2D スライスの中の N 次元データを処理するための再帰的関数を定義する方法について説明しました。この例では、4 つのプレーンの char データに制限されていましたが、多くの Jitter オブジェクトはどのような型、プレーン数でも動作します。すべての型、およびプレーン数をサポートするためには、いくつかのケースの扱いを知っておく必要があります。それは、どのようにデータをステップスルー(順次読み出し)するか、そして、データをどのような型として解釈するかといったことで、これにより適切な演算を行うことが可能になります。この問題に対するアプローチにはいくつかの方法があり、また、いくつかの最適化に関して行うべき決定があります。これらのケースの扱いは全て、多少厄介なものかも知れません。そのため、最初にオブジェクトを開発する時には、1つの型、および1つのプレーン数に対象を絞っておき、そこで演算の適切な定義を完了した上でのみ、あらゆる型を処理できるようにコードを強化することを試みたり、特定のケースにおける最適化を考えたりするという方法が、理にかなっていると言えるでしょう。C におけるマクロの使用や、C++ のテンプレートは、良いコードの再利用を調べる上で役に立つかも知れません。コードの最適化について言うと、通常、最適化を試し、実行するための適切な単位要素は、可能な限り条件分岐を避けるために「最も内部のループ」になります。

この関数は問題解決のための心臓部となるもので、あなたはこれを自分自身のカスタムオブジェクトに追加することになります。このデータを処理するための「王道」は存在しないため、再帰的なN次元処理関数のためのコードリストについては、これ以上言及しません。しかし、良いサンプルを含むSDK プロジェクトとして次のようなものがあります。jit.clip はプレーンに関してそれぞれ独立に作用し、点ごとの演算を行います(いくつかの指定範囲のための制限値を持ちます)。 jit.rgb2luma はプレーンに関して相互に依存する処理を行いますが、点ごとの演算を行います(輝度の処理のために RGB カラーをサポートします)。また、jit.transpose は、プレーンに関してそれぞれ独立に作用しますが、空間処理(行は列になります)を行います。N次元マトリックス処理に関する、これ以上のアイデアを得るためには、入手できる 2D シグナル処理や画像処理に関する書籍のうちの1つを読んでみることを推奨します。これらのコンセプトはほとんど、容易に、より高次のディメンションに対して一般化できます。

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

MOP Max ラッパークラスには通常多くのデフォルトの動作があり、 Jitter クラスアドーンメントである jit_mopとユーザが指定したフラグに基づいて max_jit_classex_mop_wrap 関数によってセットアップされます。デフォルトの動作をすべて、あるいは特定の機能だけを指定してオーバーライドすることができます。デフォルト動作をすべてオーバーライドしたい場合には、 max_jit_classex_mop_wrap() 関数の呼出しを行なう際に、 MAX_JIT_MOP_FLAGS_OWN_ALL を使います。jit_mop アドーンメントを利用する必要がある場合には、 jit_class_adornment_get() メソッドを Jitter クラス上で呼出すことによって、 jit_mop を調べることができます。jit_mop_io の入力と出力は、この章で既に述べた MOP Jitter クラス定義の中でセットされる場合と同様に問い合わせを受け付け、アトリビュートの調査に応じることができます。次の例では、jit.scalbias オブジェクトの jit_mop アドーンメントをどのように調べているかを示しています。

   // Jitter クラス名を調べます
   jclass = jit_class_findbyname(gensym("jit_scalebias"));
   // jit_mop アドーンメントを調べます 
   mop = jit_class_adornment_get(jclass,_jit_sym_jit_mop);

jit_matrix メソッドのオーバーライド

デフォルトでは、jit_matrix メソッドが自動的に追加され、マトリックスのコピーや入力されるデータに基づく演算を行ないます。最も一般的な MOP では、デフォルトのjit_matrix メソッドをそのまま使用します。しかし、デフォルトの MOP メソッドをオーバーライドし、特別な動作を行なわせる必要があるインスタンスがあります。これには、SDKの jit.op の例のように入力されるデータを記録する場合、jit.pack や jit.str.op の例のように標準のコピーや適合以外の動作を行なう場合、あるいは、jit.noise の例のように jit.matrix を全く動作しないようにしたい場合などがあります。デフォルトの jit_matrix メソッドが定義されないようにするためには、 max_jit_classex_mop_wrap() 関数を呼び出す際に MAX_JIT_MOP_FLAGS_OWN_JIT_MATRIX というフラグを使用します。独自の jit_matrix メソッドを定義するためには、jit_matrix シンボルにバインドされた A_GIMME によるメソッドを main 関数に追加します。次は、jit.op からの例です。

   // main()関数の中に jit_matrix のカスタムメソッドを追加します。
   addmess((method)max_jit_op_jit_matrix, "jit_matrix", A_GIMME, 0);

   void max_jit_op_jit_matrix(t_max_jit_op *x, t_symbol *s, short argc,
               t_atom *argv)
   {
      if (max_jit_obex_inletnumber_get(x)) 
      {
         // 右側のインプットでマトリックスを受信した場合 
         // float や into の入力を上書きするために記録します
         x->last = OP_LAST_MATRIX;
      }
      
      // ここで、デフォルトの jit_matrix メソッドを渡します 
      max_jit_mop_jit_matrix(x,s,argc,argv);
   }

jit.pack と jit.str.op の例はやや複雑ですが、デフォルトの jit_matrix メソッドが実行するタスクの種類がよくわかるでしょう。

bang および outputmatrix メソッドのオーバーライド

一般的に、MOP の Max ラッパークラスには、bang および outputmatrix メソッドがあります。通常この2つのメソッドは同じもので、デフォルトではどちらも最も最後に計算されたマトリックスを出力します。SDK のjit.3m の例のように、マトリックスの出力を行なわないオブジェクトでは、たいていの場合、これらのメッセージを独自の bang メソッド、場合によっては outputmatrix メソッドでオーバーライドしています。これらのメソッドは、 max_jit_classex_mop_wrap() 関数を呼び出す際に、 MAX_JIT_MOP_FLAGS_OWN_BANG およびMAX_JIT_MOP_FLAGS_OWN_OUTPUTMATRIX フラグを使用することによってオーバーライドできます。通常、この2つのフラグは一緒に渡されます。

name, type, dim, および planecount アトリビュートのオーバーライド

左端以外のインプットとアウトプットそれぞれには、入出力されるマトリックスについての問合せや設定を行なうことができるアトリビュートが、デフォルトで追加されます。このアトリビュートには、name (名前)、type(型)、dim (ディメンション)、planecount(プレーン数)があります。非常に特殊な処理を実行する場合、デフォルトのアトリビュートの動作をオーバーライドする必要が生じるかもしれませんが、SDK のサンプルで、これを行なっているものはありません。name、type、dim、planecount アトリビュートの追加が行なわれないようにするためには、max_jit_classex_mop_wrap() 関数の呼び出しの際に、 MAX_JIT_MOP_FLAGS_OWN_NAMEMAX_JIT_MOP_FLAGS_OWN_TYPEMAX_JIT_MOP_FLAGS_OWN_DIMMAX_JIT_MOP_FLAGS_OWN_PLANECOUNT というフラグを使用します。独自のアトリビュートの定義を行なうには、オーバーライドしたいアトリビュート名を使って、Max ラッパークラスでアトリビュートを定義する場合と同じ方法で行います。

clear および notify メソッドのオーバーライド

clear および notify メソッドはデフォルトで追加されます。デフォルトの clear メソッドは、それぞれの入力、出力マトリックスを消去します。デフォルトの notify メソッド、max_jit_mop_notify() は、MOP によって維持されているマトリックスに変更があった場合、常に呼び出されます。このデフォルトの notify(通知)に加えて、何らかの通知が必要である場合、入出力されるマトリックスに関する必要なメンテナンスを行なうことができるようにmax_jit_mop_notify 関数を呼び出すことが重要です。次に挙げる SDK の jit.notify からの例では、このことを示しています。この2つのメソッドは、 max_jit_classex_mop_wrap() 関数を呼び出す際に、それぞれ MAX_JIT_MOP_FLAGS_OWN_CLEARMAX_JIT_MOP_FLAGS_OWN_NOTIFY というフラグを使用することによってオーバーライドできます。オブジェクトの登録と通知に関する詳細は後の章で述べます。次に jit.notify の notify メソッドの例を示しておきます。

By default, a clear and a notify method are added. The default clear method clears each of the input and output matrices. The default notify method, max_jit_mop_notify(), is called whenever any of the matrices maintained by the MOP are changed. If it is necessary to respond to additional notifications, it is important to call the max_jit_mop_notify function so that the MOP can perform any necessary maintenance with respect to input and output matrices, as demonstrated by the jit.notify SDK example. These methods can be overridden using the MAX_JIT_MOP_FLAGS_OWN_CLEAR and MAX_JIT_MOP_FLAGS_OWN_NOTIFY flags, respectively, when calling the max_jit_classex_mop_wrap() function. Object registration and notification is covered in detail in a future chapter, but the jit.notify notify method is provided as an example.

// s はサーバ名, msg はメッセージ, ob はサーバオブジェクトへのポインタ, 
// data は、与えられたメッセージに対してサーバが提供する追加のデータ
void max_jit_notify_notify(
   t_max_jit_notify *x, t_symbol *s, t_symbol *msg, void *ob, void *data)
{
   if (msg==gensym("splat")) {
      post("notify: server=%s message=%s",s->s_name,msg->s_name);
      if (!data) {
         error("splat message NULL pointer");
         return;
      }
      // ここで、右端のアウトレットを使って出力を行います
      // "data# が t_atom[3] を指すことがわかっているものとします
      max_jit_obex_dumpout(x,msg,3,(t_atom *)data); 
   } else {
      // デフォルトの Max MOP 通知メソッドに渡します。
      max_jit_mop_notify(x,s,msg);
   }
}

adapt および outputmode アトリビュートのオーバーライド

adapt および outputmode アトリビュートは、MOP Max ラッパーにデフォルトで追加されます。adaptアトリビュートは、入力マトリックスへの適応を行なうかどうかを決定します。また、outputmode アトリビュートは、アウトプットで新しい出力マトリックスの演算を行なうか、あるいは最後に演算を行なったマトリックスを出力するか(freeze)、もしくは入力マトリックスをそのまま出力するか(bypass)を決定します。 max_jit_classex_mop_wrap() の呼び出しの際に、MAX_JIT_MOP_FLAGS_OWN_ADAPT、および MAX_JIT_MOP_FLAGS_OWN_OUTPUTMODE というフラグを使うと、adapt および outputmode のデフォルトのアトリビュートが追加されないようにすることができます。独自のアトリビュートの定義を行なうには、オーバーライドしたいアトリビュート名を使って、Max ラッパークラスでアトリビュートを定義する場合と同じ方法で行います。

mproc メソッドの定義

多くの演算では、デフォルトの Jit_matrix メソッドやアトリビュートの完全なオーバーライドが必要になることはありません。単に Jitter クラスの matrix_calc メソッドやアウトレット用関数を呼び出す方法をオーバーライドする必要があるだけであれば、デフォルトの処理の代わりに呼び出される mproc メソッドを定義することによって行なうことができます。jit.3m SDK プロジェクトは、Jitter クラスの matrix_calc メソッドを呼び出した後、デフォルトの jit_matirix メッセージを出力するのではなく、その代わりに Jitter クラスのアトリビュートの問合せを行い、max メッセージを出力します。

void max_jit_3m_mproc(t_max_jit_3m *x, void *mop)
{
   t_jit_err err;
   
   // 内部の JItter オブジェクトの matrix_calc メソッドを呼び出します
   if (err=(t_jit_err) jit_object_method(
      max_jit_obex_jitob_get(x),
      _jit_sym_matrix_calc,
      jit_object_method(mop,_jit_sym_getinputlist),
      jit_object_method(mop,_jit_sym_getoutputlist))) 
   {
      // エラーがあれば報告します
      jit_error_code(x,err); 
   } else {
      // Jitter クラスの問合せを行ない、アウトレットの呼び出しを行ないます
      max_jit_3m_bang(x);
   }
}

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

マトリックスオペレータ・クイックスタートで述べたように、Max クラスのコンストラクタの中では、MOP のインプット、アウトプットで必要となるマトリックスを対応するマトリックスインレット、アウトレットに割り当て、マトリックスのアーギュメントと他の MOP の設定を処理しなければなりません。さらに、デストラクタでは、MOPリソースの開放を行なわなければなりません。通常、標準の関数 max_jit_mop_setup_simple()max_jit_mop_free() を使ってこれらをすべて処理することができます。しかし、インスタンスの中には特別な処理が必要になるものもあります。

可変な入出力

max_jit_mop_setup_simple() 関数は、必要なプロキシインレット、アウトレットや内部のマトリックスを定義するために、 max_jit_mop_inputs()max_jit_mop_outputs() を呼び出します。 デフォルトの動作の例として、これらの関数のリストを示しておきます。さらに、SDK プロジェクトにある Jit.scissors、jit.glue、jit.pack、jit.unpack をより詳しく調べてみることをお勧めします。

t_jit_err max_jit_mop_inputs(void *x)
{
   void *mop,*p,*m;
   long i,incount;
   t_jit_matrix_info info;
   t_symbol *name;
   
   // オブジェクトの MOP アドーンメントを調べます
   if (x&&(mop=max_jit_obex_adornment_get(x,_jit_sym_jit_mop))) 
   {
      incount  = jit_attr_getlong(mop,_jit_sym_inputcount);
      
      // プロキシインレットの追加、および左端のインレットを除くすべての
      // インプット用のマトリックスの追加を行ないます 
      for (i=2;i<=incount;i++) {
         max_jit_obex_proxy_new(x,(incount+1)-i); // 右から左へ
         if (p=jit_object_method(mop,_jit_sym_getinput,i)) {
            jit_matrix_info_default(&info);
            max_jit_mop_restrict_info(x,p,&info);
            name = jit_symbol_unique();
            m = jit_object_new(_jit_sym_jit_matrix,&info);
            m = jit_object_register(m,name);
            jit_attr_setsym(p,_jit_sym_matrixname,name);
            jit_object_method(p,_jit_sym_matrix,m);
            jit_object_attach(name, x);
         }
      }   
      return JIT_ERR_NONE;
   } 
   return JIT_ERR_INVALID_PTR;
}

t_jit_err max_jit_mop_outputs(void *x)
{
   void *mop,*p,*m;
   long i,outcount;
   t_jit_matrix_info info;
   t_symbol *name;

   if (x&&(mop=max_jit_obex_adornment_get(x,_jit_sym_jit_mop))) 
   {
      outcount = jit_attr_getlong(mop,_jit_sym_outputcount);

      // アウトレットの追加、およびすべてのアウトプット用の内部マトリックスの追加を行ないます
      for (i=1;i<=outcount;i++) {
         max_jit_mop_matrixout_new(x,(outcount)-i);// 右から左へ
         if (p=jit_object_method(mop,_jit_sym_getoutput,i)) {
            jit_matrix_info_default(&info);
            max_jit_mop_restrict_info(x,p,&info);
            name = jit_symbol_unique();
            m = jit_object_new(_jit_sym_jit_matrix,&info);
            m = jit_object_register(m,name);
            jit_attr_setsym(p,_jit_sym_matrixname,name);
            jit_object_method(p,_jit_sym_matrix,m);
            jit_object_attach(name, x);
         }
      }

      return JIT_ERR_NONE;
   } 
   return JIT_ERR_INVALID_PTR;
}

マトリックスアーギュメント

max_jit_mop_setup_simple() 関数は任意のマトリックスアーギュメントを取得するために max_jit_mop_matrix_args() 関数を呼び出します。そして、マトリックスが存在していれば、リンクされたインプット、アウトプットを送り、adapt アトリビュートを無効にします。次のリストはデフォルトの動作を示しています。

t_jit_err max_jit_mop_matrix_args(void *x, long argc, t_atom *argv)
{
   void *mop,*p,*m;
   long incount,outcount,attrstart,i,j;
   t_jit_matrix_info info,info2;
      
   if (!(mop=max_jit_obex_adornment_get(x,_jit_sym_jit_mop)))
      return JIT_ERR_GENERIC;

   incount  = jit_attr_getlong(mop,_jit_sym_inputcount);
   outcount = jit_attr_getlong(mop,_jit_sym_outputcount);
   
   jit_matrix_info_default(&info);
   
   attrstart = max_jit_attr_args_offset(argc,argv);
   if (attrstart&&argv) {
      jit_atom_arg_getlong(&info.planecount, 0, attrstart, argv);
      jit_atom_arg_getsym(&info.type, 1, attrstart, argv);
      i=2; j=0;
      while (i<attrstart) { //ディメンション
         jit_atom_arg_getlong(&(info.dim[j]), i, attrstart, argv);
         i++; j++;
      }
      if (j) info.dimcount=j;
      
      jit_attr_setlong(mop,_jit_sym_adapt,0); //adaptをオフにします
   }
   
   jit_attr_setlong(mop,_jit_sym_outputmode,1); 
      
   for (i=2;i<=incount;i++) {
      if ((p=jit_object_method(mop,_jit_sym_getinput,i)) &&
         (m=jit_object_method(p,_jit_sym_getmatrix)))
      {
         jit_object_method(m,_jit_sym_getinfo,&info2);
         if (jit_attr_getlong(p,_jit_sym_typelink)) {
            info2.type = info.type;
         }
         if (jit_attr_getlong(p,_jit_sym_planelink)) {
            info2.planecount = info.planecount;
         }
         if (jit_attr_getlong(p,_jit_sym_dimlink)) {
            info2.dimcount = info.dimcount;
            for (j=0;j<info2.dimcount;j++) {
               info2.dim[j] = info.dim[j];
            }
         }
         max_jit_mop_restrict_info(x,p,&info2);
         jit_object_method(m,_jit_sym_setinfo,&info2);
      }
   }

   for (i=1;i<=outcount;i++) {
      if ((p=jit_object_method(mop,_jit_sym_getoutput,i)) &&
         (m=jit_object_method(p,_jit_sym_getmatrix)))
      {
         jit_object_method(m,_jit_sym_getinfo,&info2);
         if (jit_attr_getlong(p,_jit_sym_typelink)) {
            info2.type = info.type;
         }
         if (jit_attr_getlong(p,_jit_sym_planelink)) {
            info2.planecount = info.planecount;
         }
         if (jit_attr_getlong(p,_jit_sym_dimlink)) {
            info2.dimcount = info.dimcount;
            for (j=0;j<info2.dimcount;j++) {
               info2.dim[j] = info.dim[j];
            }
         }
         max_jit_mop_restrict_info(x,p,&info2);
         jit_object_method(m,_jit_sym_setinfo,&info2);
      }
   }
      
   return JIT_ERR_NONE;
}

Copyright © 2008, Cycling '74