Max 5 API Reference
あなたが作成したMax オブジェクトはC データ構造体で、そこではメソッドが動的に関数にバインドされます。
あなたのオブジェクトのメソッドは Max から呼び出されますが、オブジェクト自身でメソッドを呼び出すことも可能です。メソッドを呼び出すとき、呼び出すメソッドが型を持つものかどうかを知ることは重要です。
型を持ったメソッドを呼び出す場合、アーギュメントをアトムの配列として渡す必要があります。型を持たないメソッドを呼び出す場合には、メソッドを実装し ているCの関数の引数を正確に知っていなければなりません。どちらの場合でも、メソッドの名前をシンボルとして渡します。
型を持ったメソッドの場合、Max はアトムの配列を取り、これを、メソッドの引数の型指定子リストに基づいた引数としてオブジェクトに送ります。例えば、メソッドが引数として型指定子リスト A_LONG, を持つように宣言されている場合、渡される配列の最初のアトムは int に変換され、スタック上の関数に渡されます。アーギュメントが与えられない場合、型指定子として A_LONG、0 という型を持ったメソッドの呼び出しは失敗します。型を持ったメソッドを呼び出すためには、object_method_typed() あるいは、typedmess()を使います。
型を持たないメソッドの場合、Max は単にオブジェクト内のシンボルを検索し、これに合致した関数が見つかった場合には、あなたが渡した引数でこの関数を呼び出します。
オブジェクトのために特定のメソッドを書く場合、例えばオブジェクトについて記述する assist メソッドや、オーディオオブジェクトの DSP メソッドのようなメソッドは、 A_CANT という引数の型指定子を用いて、型を持たないメソッドとして宣言されます。これは、このメソッドに渡した引数の型チェックをMax が行なわないことを意味します。しかし、最も重要なことは、ユーザがこのオブジェクト(のこのメソッド)に対してメッセージボックスを接続することができ ず、メッセージを送ることによって型を持たないメソッドを呼び出すことができないという点です。(標準的な Max オブジェクトに assist メッセージを送って、実際に試してみて下さい)
アウトレットを使用する場合、事実上、アウトレットに接続されたあらゆるオブジェクトで型を持ったメソッドの呼び出しを行なっています。
アトリビュートはオブジェクト内のデータの記述です。これらの記述を標準化することにより、Max ではオブジェクトデータに対する豊富なインターフェイスを提供することができるようになっています。 このインターフェイスには、pattr システム、インスペクタ、クイックリファレンスメニュー、@ アーギュメントなどが含アトリビュートはオブジェクト内のデータの記述です。これらの記述を標準化することにより、Max ではオブジェクトデータに対する豊富なインターフェイスを提供することができるようになっています。 このインターフェイスには、pattr システム、インスペクタ、クイックリファレンスメニュー、@ アーギュメントなどが含まれます。UI オブジェクトを書いてみようとするのであれば、アトリビュートを理解しておくことは重要です。しかし、非UI オブジェクトであっても、同様にアトリビュートを利用することができます。以下の説明は、UI オブジェクトに特有なことではありません。アトリビュートの定義は、attribute のアトリビュート(attr attrs)を使って記述されるだけではなく、近年導入された、ext_obex_util.h ( ext_obex.hにインクルードされています) for defining attributes, as well as describing them using attributes of attributes (attr attrs). の中のマクロシステムを使用します。これらの基礎となっているアトリビュート定義メカニズムに関する関数ごとの詳細な説明は、Attributes リファレンスを参照して下さい。
アトリビュートはオブジェクトの特定のインスタンスに対して定義することができます。しかし、クラスに対してアトリビュートを定義することのほうが一般的です。このような場合、クラスの個々のインスタンスは同じアトリビュート項目を持っていますが、その値はインスタンスに特有なものになります。ここでは、クラス・アトリビュートだけに焦点をあてて説明します。
アトリビュートが宣言され、ユーザによる設定が可能になると、ユーザはアトリビュート名とアーギュメントから成るメッセージをオブジェクトに送ることができます。このメッセージはアトリビュートの新しい値を示すものです。例えば、trackcount というアトリビュートを宣言した場合、メッセージ trackcount 20 はtrackcount アトリビュートを 20 に設定します。この操作を可能にするために、特別なことを行なう必要はありません。加えて、ユーザが設定可能なアトリビュートは、オブジェクト上でユーザがインスペクタを開いたときに表示されます。
アトリビュートをオフセットアトリビュート(offset attribute)として定義した場合、その位置(とサイズ)をオブジェクトの C データ構造体の中に記述します。すると、Max はこのデータを直接に読み書きすることができるようになります。また、アトリビュートの値が、単純に数値を格納するようなものではなく、より複雑な場合、 独自のゲッタールーチンやセッタールーチン(カスタムセッタールーチンやカスタムゲッタールーチン)を定義することもできます。これは理論的な例ですが、 オブジェクトに地球の人口を示すアトリビュートを持たせることも可能です。この値がオブジェクトの内部に保存できない場合、結果を返す前に、カスタムゲッ タールーチンで世界規模の国勢調査を行なうことも可能です。この値がゼロに設定されていたとしたら、地球の人口を設定するカスタムセッタールーチンが何か 汚い手を使ったのかもしれません。もしあなたが人間嫌いでないのであれば、このようなアトリビュートを読み出し専用に設定する機能を利用することもできま す。
アトリビュートの定義は、初期化ルーチンの中でメソッドを定義するときに行なわれます。メソッドの定義を行なう前にアトリビュートを定義することも可能で すが、慣例として、一般的にメソッドの後に定義されます。それぞれの定義では、アトリビュートの名前、サイズ、オブジェクトデータ構造体の中の対応するメ ンバのオフセットを指定します。このオブジェクトデータ構造体の中にデータが保持されます。例えば、仮にオブジェクトが次のように定義されたとしましょ う。
typedef struct _myobject { t_object m_ob; long m_targetaddress; t_symbol *m_shipname; char m_compatmode; } t_myobject;
ここで、m_targetaddress、m_shipname、m_compatmode に対するアトリビュートを作成する必要があるとします。ext_obex_util.hの中には、 それぞれのデータ型(および他のいくつかのデータ型)のためのマクロがあります。これにより、かなりな量のタイピングを節約できます。このようにして、例 えば、CLASS_ATTR_LONG を使うm_targetaddresのためのアトリビュートを定義することができます。次に示すのは、上に挙げたデータ構造体のメンバすべてのためのアト リビュート定義です。
CLASS_ATTR_LONG(c, "targetaddress", 0, t_myobject, m_targetaddress); CLASS_ATTR_SYM(c, "shipname", 0, t_myobject, m_shipname); CLASS_ATTR_CHAR(c, "compatibilitymode", 0, t_myobject, m_compatmode);
あなたのオブジェクトの中のデータを Max に直接読み書きさせるだけでは十分でない場合もあるでしょう。また、(前の例の世界の人口のように)値として返す前に演算を行なう必要があルデータを持っ ている場合もあるでしょう。あるいは、アトリビュートの値が変更されたときに、他のオブジェクトのステートを更新するために何らかの処理が必要になる場合 もあるかもしれません。このような複雑なケースを扱うために、カスタムアトリビュートゲッタルーチン(独自のアトリビュートゲッタルーチン)やカスタムア トリビュートセッタルーチン(独自のアトリビュートセッタルーチン)を定義することができます。ゲッターはアトリビュートの値にアクセスする場合に呼び出 されます。セッターは、アトリビュートの値を変更する場合に呼び出されます。
例として、数値の配列を保持するオブジェクトがあると仮定し、この配列のサイズを表すアトリビュートを作りたい場合を考えてみましょう。アトリビュートの 値が変更された場合、配列の大きさを変更する必要があるため、このアトリビュートに対してカスタムセッターを定義します。配列のサイズをオブジェクト内に 格納するのであれば、デフォルトのゲッタで十分ですが、ここではアトリビュートゲッタの書き方を説明することが狙いであるため、配列に割り当てているメモ リポインタのサイズから配列のサイズを計算するコードを書いています。まず、このオブジェクトのデータ構造体は次のようになっているとします。
typedef struct _myobject { t_object m_ob; long *m_data; } t_myobject;
カスタムアトリビュートセッタ、ゲッタのプロトタイプは次のように宣言されているとします。
t_max_err myobject_size_get(t_myobject *x, t_object *attr, long *argc, t_atom **argv); t_max_err myobject_size_set(t_myobject *x, t_object *attr, long argc, t_atom *argv);
次は、カスタムセッタ、ゲッタを定義するために、CLASS_ATTR_ACCESSORS マクロを使ってアトリビュートを定義する方法を示すものです。ここでは、カスタムセッタ、ゲッタのための「オフセット」を実際には使用していないため、ダ ミーとして任意の構造体メンバを渡すことができます(デフォルトのアトリビュートゲッタ、セッタだけがこのオフセットを使用しますが、これらは検討の対象 とはしていません)。
CLASS_ATTR_LONG(c, "size", 0, t_myobject, m_ob); CLASS_ATTR_ACCESSORS(c, "size", myobject_size_get, myobject_size_set);
配列のサイズをセットするカスタムセッタの実装は次のようになります。セッタでは、system_resizeptr という便利な Max API 関数を使っているため、1回のステップで、効果的に配列の「サイズ変更」とその中へのデータのコピーを実現しています。セッタではアトムを使用するため、 argv 配列の最初の要素から値を取得しておく必要があります。
t_max_err myobject_size_set(t_myobject *x, t_object *attr, long argc, t_atom *argv) { long size = atom_getlong(argv); if (size < 0) // 間違ったサイズ。この場合何も変更しません。 return 0; if (x->m_data) x->m_data = (long *)sysmem_resizeptr((char *)x->m_data, size * sizeof(long)); else // 最初のメモリ割り当て x->m_data = (long *)sysmem_newptr(size * sizeof(long)); return 0; }
ゲッタのアクセスもアトムを使って行なわれますが、返されるものはアトムの配列へのポインタです。ゲッタを呼び出す側には2つの選択肢があります。その1 つは、あらかじめメモリを割り当てておく方法(argc で配列の長さを、argv でメモリへのポインタを渡します)、もう1つは argc として 0 を、argvの内容を NULL にして渡し、ゲッタにメモリを割り当ててもらう方法です。このケースを扱う最も簡単な方法は、atom_alloc というユーティリティ関数を呼び出すことです。この関数は渡されたものを解釈し、必要であれば返されたアトムにメモリを割り当てます。
t_max_err myobject_size_get(t_myobject *x, t_object *attr, long *argc, t_atom **argv) { char alloc; long size = 0; atom_alloc(argc, argv, &alloc); // allocate return atom if (x->m_data) size = sysmem_ptrsize((char *)x->m_data) / sizeof(long); // calculate array size based on ptr size atom_setlong(*argv, size); return 0; }
オブジェクトのアトリビュートが1つでも変更されると、オブジェクトは "notify" メッセージを受信します。カスタムセッタを書く代わりに、これを利用することができます。notify メソッドのプロトタイプは次のようなものです。
クラスの初期化の際に次の行を追加すると、notify メソッドが呼び出されるようになります。
class_addmethod(c, (method)myobject_notify, "notify", A_CANT, 0);
notify メソッドは様々なノーティフィケイション(通知)を扱うことができます(これに関するドキュメントは後日公開します)。しかし、ここで注目するのは "attr_modified" です。ノーティフィケイション・タイプは notify メソッドの msg という引数に渡されます。次に示すのは、変更されたアトリビュート名を出力する notfy メソッドの例です。この出力処理の代わりに、任意の処理を行なうことが可能です。アトリビュートの名前を取得するために、notify メソッドの 引数 data をアトリビュートオブジェクトとして解釈しています。アトリビュートは標準的な Max オブジェクトであるため、これにメッセージを送るために object_method を使うことができます。このケースでは、メッセージ getname を attribute オブジェクトに送り、名前を取得しています。
t_max_err myobject_notify(t_myobject *x, t_symbol *s, t_symbol *msg, void *sender, void *data) { t_symbol *attrname; if (msg == gensym("attr_modified")) { // check notification type attrname = (t_symbol *)object_method((t_object *)data, gensym("getname")); // ask attribute object for name object_post((t_object *)x, "changed attr name is %s",attrname->s_name); } return 0; }