Max 5 API Reference

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

 

この章の目的は、ビデオストリーム用に最も一般的に用いられるマトリックス(すなわち、4つのプレーンの char データ)の処理を行なうことができるシンプルなマトリックスオペレータ(MOP)を開発する方法に関して、簡単で高レベルな概要を示すことです。そのために、ここでは SDK サンプル jit.scalebias を使います。(訳注:「高レベルー低レベル」は、プログラミング言語に関して使用される概念で、一般的に「低レベル」は、よりマシン語に近い具体的なもの、「高レベル」は、より抽象化されたものを表します。)複数のデータ型、プレーン数、ディメンション、入出力などを扱うマトリックスオペレータを作る方法の詳細に関しては次の章で説明しています。この章では、前の章までの Jitter オブジェクトモデルや Max ラッパークラスについてだけでなく、Jiiter チュートリアルで述べられているような Jitter の多次元マトリックス表現や、Max パッチャーからのマトリックスオペレータの使用に関しても熟知していることを前提にしています。

MOP Jitter クラスの定義

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

   // 1インプット、1アウトプットを持つ jit_mop の新しいインスタンスを作る
   mop = jit_object_new(_jit_sym_jit_mop,1,1);
   
   // すべてのインプット、アウトプットに対し、シングルタイプを強制する
   jit_mop_single_type(mop,_jit_sym_char);   

   // すべてのインプット、アウトプットに対し、シングルのプレーン数を強制する
   jit_mop_single_planecount(mop,4);   

   // クラスのアドーンメントとして jit_mop オブジェクト を追加する
   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_add_adornment() 関数を使って、これをあなたの 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, 0);
   } 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;
}

Processing N-Dimensional Matrices

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);
}

Copyright © 2008, Cycling '74