このセクションでは、エクスターナルオブジェクトのメソッドを書く上での、重要な4つの構造について詳しく述べられています。それは、Outlet、Binbuf、Qelem、Clockです。加えて、Maxとのインターフェイスとして使用できる、重要なユーティリティルーチンについても詳細に述べられています。
ここで紹介する関数は、他のオブジェクトにデータを送るために使用されます。アウトレットのデータ構造体のフィールドにアクセスする必要はありません。すべての関数はアウトレットからデータを送り、データの転送中にスタックオーバーフローが起こった場合に0を返します。繰り返しoutlet 関数を呼び出す場合、0が返されたことが判明したら処理を中止しなければなりません。例えば、
for (i=0; i < count; i++) if (!outlet_int(myObject, (long)i)) break;
ゼロでない値は、エラーが生じなかったことを示します。
outlet_bang |
||
bang メッセージをアウトレットから送信する場合に、outlet_bang を使います。 | ||
void *outlet_bang (Outlet *theOutlet); | ||
theOutlet | メッセージを送信するアウトレット |
outlet_float |
||
float メッセージをアウトレットから送信する場合に、outlet_float を使います。 | ||
void *outlet_float (Outlet *theOutlet, double f); | ||
theOutlet | メッセージを送信するアウトレット | |
f | 送信されるfloat値 |
outlet_int |
||
intメッセージをアウトレットから送信する場合に、outlet_intを使います。 | ||
void *outlet_int (Outlet *theOutlet, long n); | ||
theOutlet | メッセージを送信するアウトレット | |
n | 送信される整数(int)値 |
outlet_list |
||
listメッセージをアウトレットから送信するために、outlet_listを使います。 | ||
void *outlet_list (Outlet *theOutlet, t_symbol *msg, short argc, t_atom *argv); | ||
theOutlet | メッセージを送信するアウトレット | |
msg | 0Lでなくてはなりませんが, listシンボルの場合もあります。 | |
argc | argvの中のlistの要素数 | |
argv | listを構成するAtom | |
outle_list はargv と argcで指定されたリストを指定されたアウトレットから送信します。アウトレットはオブジェクト生成関数の中の listout か outlet_new(上を参照)で事前に作成しておく必要があります。リストは Atom 型の配列として作成しますが、リストの最初の要素は整数または浮動小数でなくてはなりません。 これは3つの数値から成るリストを送信する例です。 t_atom myList[3]; long theNumbers[3]; short i; theNumbers[0] = 23; theNumbers[1] = 12; theNumbers[2] = 5; for (i=0; i < 3; i++) { SETLONG(myList+i,theNumbers[i]); /* t_atomをセットするマクロ */ } outlet_list(myOutlet,0L,3,&myList); ローカル(auto)変数から成る大きなリストを outlet_list に渡すのは良い方法とは言えません。上の例のようにリストが小さい場合は問題ありませんが、オブジェクトが定期的にリストを送信する場合は、オブジェクトのデータ構造体の中にt_atomの配列を保持しておくとよいでしょう。 |
outlet_anything |
||
様々なメッセージを送信するアウトレットを作成するために、outlet_anythingを使います。 | ||
void *outlet_anything (Outlet *theOutlet, t_symbol *msg, short argc, t_atom *argv); | ||
theOutlet | メッセージを送信するアウトレット | |
msg | t_symbol型メッセージのセレクタ | |
argc | argvのリストの要素の数 | |
argv | メッセージのアーギュメントを構成するt_atom | |
この関数は、アウトレットから任意のメッセージを送信します。2つの使用例を次に示します。 最初の例は、bang メッセージを送信する難しい方法です。(前述の outlet_bang を使うと簡単に行えます。) outlet_anything(myOutlet, gensym("bang"), 0, NIL); そして、次の例は1つの整数を送信する同様に難しい方法です。( outlet_int の代りに使えます) t_atom myNumber; myNumber.a_type = A_LONG; /* Atomに型を代入*/ myNumber.a_w.w_long = 432; /* 値を代入 */ /* 上の2行の代りに、SETLONGマクロを使用することもできます*/ outlet_anything(myOutlet, gensym("int"), 1, &myNumber); outlet_anything はメッセージのアーギュメントとして t_symbol 型を要求するため、文字列に対して gensym(C文字列をt_symbol型に変換します)を適用しなければならない点に注意して下さい。同じメッセージを多数送信したい場合、初期化時にメッセージ文字列に対して gensym を呼び出し、その結果をグローバル変数に保存することによって(重要です)、メッセージを送信するたびに gensym を呼び出すオーバーヘッドを避けることができます。また、リストを送信するために、リストをセレクタの引数として outlet_anything を使わないで下さい。その代りに outlet_list 関数を使用します。 |
FileメニューのOpen As Text…を選ぶと、Maxバイナリファイルをテキストファイルとしてオープンすることができます。すばらしいことですが、これは何を意味するのでしょうか?1つは、Maxバイナリファイルをパッチャーウィンドウに展開しなくても変換できるということです。さらに、これによって Maxがメッセージを保存するBinbuf(binary buffer-バイナリバッファの略です)と呼ばれるメカニズムを調べることができるということです。。
BinbufはMaxのテキストファイルを「Atom化」したものです。パッチの一部分をコピーまたは複製したとき、これはBinbufに変えられます。BinbufはMaxメッセージからできているため、「評価する」ことができます。
次の例は、パッチャーウィンドウに + オブジェクトと3つのナンバーボックスがある簡単なMaxファイルを1行ずつの注釈を加えて表したものです。(この例では、特定のオブジェクトからフォント情報とカラー情報を除いて簡略化してあります。)
max v2; |
|
#N vpatcher 50 38 450 338; |
|
#P number 138 67 35 0; |
|
#P number 98 67 35 0; |
|
#P number 98 168 35 0; |
|
#P newex 98 127 50 0 +; |
|
#P connect 0 0 1 0 |
|
#P connect 3 0 0 1; |
|
#P connect 2 0 0 0; |
|
#P pop; |
|
このファイルが読み込まれると、Atom(シンボル、数値、セミコロン等)からなるBinbufに変換されます。これはメッセージとして評価され、最初のシンボルは受け手、2番目はメッセージ、後に続くAtomはメッセージのアーギュメントとみなされます。セミコロンはメッセージを切り分けるために(セパレータとして)使用されます。
Binbufについて知ることはどんな意味があるのでしょうか?1つの理由は、これがテキストを評価するために用いられるからです。一連のテキストは、 Binbufに変換されるとMaxメッセージとして「評価」されます。例えば、ポップアップメニューである umenu オブジェクトは、テキストを評価が可能なBinbufに変換し、右アウトレットからメッセージとして送信します。Binbufは注意深くテキストのストリームをAtomに切り分けてくれると同時に、シンボルを生成し、テキストの順に数値を切り分けます。さらに、Maxファイルにオブジェクトの状態を保存するために特別な処理を行いたいと思う場合にも、Binbufについて知る必要があります。これは、特にユーザインターフェイスオブジェクトを書こうとする場合にあてはまります(ノーマルオブジェクトには、このためにボックス座標とタイプインアーギュメントが保存されています)。
Binbufの内部構造について知る必要はありません。そのため、void *型を使用して参照を行なうことができます。
save メソッド(保存を行なうメソッド)を持つオブジェクトやユーザインターフェイスオブジェクトを書く場合、オブジェクト生成情報の保存のために binbuf_vinsert を使うことがよくあります。詳しい追加情報はChapter 11で述べられています。
binbuf_new |
||
Binbufを作成し、初期化するためにbinbuf_newを使います。 | ||
Binbuf *binbuf_new (void); | ||
binbuf_newはBinbufの作成に成功するとそれを返し、失敗すると0を返します。Binbufを作成した場合は、freeobject を使ってそれを取り除く(解放する)必要が生じます。 |
binbuf_append |
||
Binbufを修正せずにt_atomを追加する場合、binbuf_append を使います。 | ||
void binbuf_append (Binbuf *bin, t_symbol *msg, short argc, t_atom *argv); | ||
bin | アイテムを受け取るBinbuf | |
msg | 無視されます、0を渡します。 | |
argc | argv配列のアイテム数。 | |
argv | Binbufに追加されるatomの配列。 |
binbuf_insert |
||
セミコロンを付け加えてMaxメッセージをBinbufに追加する場合、binbuf_insert を使います。 | ||
void binbuf_insert (Binbuf *bin, t_symbol *msg, short argc, t_atom *argv); | ||
bin | アイテムを受け取るBinbuf | |
msg | 無視されます、0を渡します。 | |
argc | argv配列のアイテム数。 | |
argv | Binbufに追加されるatomの配列。 | |
オブジェクトをBinbuf に保存し、最後にセミコロンを付け加えたい場合、 binbuf_append の代わりに binbuf_insert を使います。メッセージが、パッチャーファイルのような、後で評価されるファイルの一部である場合、最初のアーギュメント argv[0] はメッセージのレシーバ(受け手)になります。このアーギュメントはシンボルでなければなりません。binbuf_vinsert(後述)は binbuf_insert より簡単に使うことができます。binbuf_vinsert ではあらかじめデータをAtom型の配列にフォーマットしておく必要がないためです。 binbuf_insert はまた、t_symbol #1〜#9を、$1〜$9に変換します。これはアーギュメントを取るパッチャーファイルを保存する時に使います。おそらく、処理の一部としてこれらのシンボルを保存することはないでしょう。 |
binbuf_vinsert |
||
セミコロンを付け加えてMaxメッセージをBinbufに追加する場合、binbuf_vinsertを使います。 | ||
void binbuf_vinsert (Binbuf *bin, char *fmtString, void *items, ...); | ||
bin | 要求されるAtomを含むBinbuf。 | |
fmtString | メッセージの個々の要素の型に対応する1つ以上の文字を含むC文字列。s はシンボル、l はlong、f はfloat。 | |
items | メッセージの要素。シンボル、long、floatとして関数に直接渡されます。 | |
binbuf_vinsert は Binbufのための printf のような機能を持っていると言えます。これは、異なった型のアーギュメントの値を渡し、Binbuf に挿入することができます。メッセージ全体はセミコロンで終わります。 binbuf_vinsert に渡すことができる要素の数は16個 までです。 下の例は、ノーマルオブジェクトによる save(保存)メソッドの実装例です。saveメソッドは#N(新しいオブジェクト)で始まるメッセージが作られることを要求し、その後にオブジェクトの名前(この例では、myobjectシンボルで表されています)、そしてインスタンス生成関数が要求するすべてのアーギュメントが続きます。この例では、long型で定義されたm_val1、mval_2という2つのフィールドの値が保存されます。 void myobject_save (myObject *x, Binbuf *dstBuf) { binbuf_vinsert(dstBuf, "ssll", gensym("#N"), gensym("myobject"),x->m_val1, x->m_val2); } このようなオブジェクトが、このデータをファイルに書き出したと考えて下さい。ファイルがテキストとして開かれると、次のようになっているでしょう。 #N myobject 10 20; #P newobj 218 82 30 myobject; 最初の行で、新しいmyobjectオブジェクトが作成されます。そして生成関数はアーギュメント10と20を受け取ります。2行目にはオブジェクトボックスのテキストが含まれています。パッチャーへのnewobjメッセージは、ユーザインタフェイスオブジェクトであるオブジェクトボックスを生成し、その前に作成されたmyogject オブジェクトに取り付けます。通常、newexメッセージが使われます。このようにして、オブジェクトボックスにタイプされたアーギュメントを使ってオブジェクトが生成されます。 |
binbuf_eval |
||
Binbufの中のMaxメッセージを評価し、そのアーギュメントを渡すために、binbuf_evalを使います。 | ||
short *binbuf_eval (Binbuf *bin, short argc, t_atom *argv, void *receiver); | ||
bin | メッセージを含むBinbuf。 | |
argc | argv配列のアイテムの数。 | |
argv | t_atomの配列、メッセージへのアーギュメント。 | |
receiver | メッセージのレシーバ。 | |
binbuf_eval は高機能な関数で、Binbufの中のメッセージを、argv のアーギュメントとともに評価して、レシーバに送信します。メッセージ送信の結果を返します。 |
binbuf_getatom |
||
ある1つのAtomをBinbufから取り出したい場合に、binfbuf_getatom を使います。 | ||
short binbuf_getatom (Binbuf *bin, long *typeOffset,long *stuffOffset, t_atom *result); | ||
bin | 取り出そうとするt_atomが含まれているBinbuf。 | |
typeOffset | Binbufの配列の型のためのオフセット。次のt_atomを指すよう修正されます。 | |
stuffOffset | Binbufの配列のデータのためのオフセット。次のt_atomを指すよう修正されます。 | |
result | 取り出されるデータが置かれているt_atomの位置(場所)。 | |
最初のt_atomを得たい場合には、typeOffset と stuffOffset の両方を0にします。binbuf_getatom は指定されたオフセットに t_atom がない場合は1を返します。また、該当する t_atom がある場合は0を返し、そのt_atomはresult として得られます。 次の例は、Binbufのすべてのアイテムを得るものです。 t_atom holder; long to, so; to = 0; so = 0; while (!binbuf_getatom(x, &to, &so, &holder)); /* t_atomを使った処理を行います。 */ |
binbuf_set |
||
Binbufの中身をすべて変更したい場合には、binbuf_set を使います。 | ||
void binbuf_set (Binbuf *bin, t_symbol *msg,short argc, t_atom *argv); | ||
bin | アイテムを受信するBinbuf。 | |
msg | 無視されます。0を渡します。 | |
argc | argv配列のアイテムの数。 | |
argv | Binbufに置かれるt_Atom型の配列。 | |
それ以前のBinbufの内容は破棄されます。 |
binbuf_text |
||
テキストハンドルを binbuf に変換する場合に、binbuf_text を使用します。 | ||
short binbuf_text (Binbuf *bin, char **srcText, long length); | ||
bin | 変換されたテキストを含むBinbuf。 事前に binbuf_new によって作成しておく必要があります。また、すでに持っている内容は破棄されます。 | |
srcText | 変換されるテキストへのハンドル。これは0で終わる必要はありません。 | |
length | テキストの文字数。 | |
binbuf_text は、srtTextハンドルにあるテキストを分析してバイナリ形式に変換します。これは、テキストファイルや入力されるテキスト行を評価してBinbufに入れるために使用します。binbuf_text は処理中にエラーが発生すると0でない値を返します。それ以外の時は0を返します。 注:テキストがbinbuf 化される際には、カンマ、1〜9の数を伴うドルマーク($1.....$9)を含むシンボル、セミコロンは特別な「pseudo(疑似)型定数」として認識されます。 次のような、binbuf_getatom によって返されるAtomのa_typeフィールドの定数 A_SEMI(10) 、A_COMMA(11) 、 A_DOLLAR(12) は特別なシンボルとして認識されます。 pseudo(疑似)型のA_DOLLARのAtomのために、t_atomの a_w.w_long フィールドは、オリジナルのテキストまたはシンボルの中のドルマーク($)に続く数値を持ちます。 このような pseudo型の使用は、入力言語の文(センテンス)や語句(フレーズ)の切り出しを設計する手助けとなります。例えば、ポップアップのumenu オブジェクトは、必要なメニュー項目をカンマによって区切ることによって、ユーザが項目の語の中にスペース文字(空白)を使用することを可能にしています。 binbuf_getatom を利用すると、メニューに表示されるatom化されたテキストを読む際、新しい項目の始まりを決定するカンマを無理なく、簡単に探すことができます。 シンボルの中でカンマやセミコロンを文字として使用したい場合は、バックスラッシュ( \ )を前につけます。バックスラッシュを2回続けると「バックスラッシュ文字」を表すことができます。 |
binvuf_totext |
||
BInbuf をテキストへのハンドルに変換する場合に、bunbuf_totext を使用します。 | ||
short binbuf_totext (Binbuf *bin, char **dstText, long *size); | ||
bin | テキストに変換するデータを含むBinbuf。 | |
dstText | テキストを渡すためにあらかじめ作っておいたハンドル。 dstTextはテキストに適合するようにサイズ変更されます。 | |
size | ここに、binbuf_totext が変換したテキストハンドルに含まれる文字数数を渡しま す。 |
|
binbuf_totext はBinbufをテキストに変換し、ハンドルに割り当てます。シンボルに含まれるカンマ文字やセミコロン文字を保護するためにバックスラッシュが追加されます。pseudo型はカンマ、セミコロン、ドルマーク($)と数値の組み合わせに変換され、その前にバックスラッシュは置かれません。binbuf_text はbinbuf_totext の出力を読むことが可能なため、同じBinbufを生成することができます。binbuf_totext が処理中にメモリ範囲を超えるようなエラーを起こした場合には、0以外の値を返し、そうでない場合0を返します。 |
binbuf_read |
||
MaxフォーマットのバイナリファイルまたはテキストファイルをBinbufに読み込む場合に、binbuf_readを使います 。 | ||
short binbuf_read (Binbuf *bin, char *filename, short volume, short binaryFlag); | ||
bin | ファイルが読み込まれるBinbuf。事前に binbuf_new によって生成されている必要があります。それまでに持っていた内容は破棄されます。 | |
filename | 読み込むファイル名のC文字列。部分的なパス名も使用できます。 | |
vol | ファイルのボリュームまたは作業ディレクトリの参照番号 。 | |
binaryFlag | 0でない場合、ファイルがバイナリファイルのMaxドキュメント(タイプmaxb)であるこ とを示します。0の場合、ファイルがテキストファイルであることを示します。古いフォーマットのMaxバイナリファイル(タイプ1)を読もうとするとエラーが返されます。 |
|
binbuf_read はファイルを開いて Binbuf に読み込みます。処理中にエラーが起こった場合は0以外の値を返し、エラーがない場合は0を返します。 |
binbuf_write |
||
Binbufの内容をMaxフォーマットのバイナリファイルまたはテキストファイルに書き込む場合、binbuf_write を使用します。 | ||
short binbuf_read (Binbuf *bin, char *filename, short volume, short binaryFlag); | ||
bin | ディスクに書き込むBinbuf | |
filename | 書き込むファイル名のC文字列. 部分的なパス名も使用できます。ファイルがすでに存在する場合には上書きされます。 | |
vol | ファイルのボリュームまたは作業ディレクトリの参照番号 。 | |
binaryFlag | 1は古いフォーマットのMaxバイナリファイル、2は新しいフォーマットのMaxバイナリファイル、0はテキストフォーマットファイルでの書き込みを指定します。PowerPC上で古いフォーマット(タイプ1)のバイナリファイルを書き込もうとするとエラーが返されます。 |
|
binbuf_writeは(必要なら)ファイルを作成し、Binbuf をそこに書き込みます。binbuf_writeは処理中にエラーが生じた場合には0以外の値を返し、エラーが生じた場合には0を返します。 |
readatom |
||
テキストバッファから1つのAtomを読み込む場合に、readatom を使用します。 | ||
short readatom (char *outstr, char **text, long *index, long size, t_atom *result); | ||
outstr | 256文字のC文字列。これは次のテキストアイテムがバッファから読み込まれる際に受け取られます。 | |
text | 読み出されるテキストバッファへのハンドル 。 | |
index | 0で始まり、readatomがテキストバッファの次のアイテムを指す際に修正されます 。 | |
size | text の文字数 。 |
|
result | テキストバッファから読み込んだAtomが格納されます 。 |
|
この関数は、binbuf_text によって用いられる、低レベルでのMaxのテキストエバリュエータ(評価器)機能を提供します。これは次の例のように、ループの中からコールされ、文字(text)のハンドルを操作するために設計されています。readatom は読み込むテキストが存在する間は0以外の値を返し、テキストの最後まで到達した場合に0を返します。この戻り値は binbuf_getatom と反対の関係になっているので注意して下さい。 long index; t_atom dst; char outstr[256]; index = 0; while (readatom(outstr,textHandle,&index,textLength,&dst)) { /* 読み込んだ Atom を使って何らかの処理を行ないます*/ } readatom を使用する代りに、binbuf_text を使ってテキストをBinbufに変換し、ループの中で binbuf_getatom を呼び出す方法もあります。 |
Atombuf はBinbuf の代りにAtomを一時的に保存するものです。これを使った場合、内部構造がパブリックに利用できるため、Atomを適切に操作することができるという利点があります。標準的なMaxのテキストオブジェクト(メッセージボックス、オブジェクトボックス、コメント)はテキストを格納するために(テキストの個々の語はシンボルまたは数値として格納されます。)Atombuf 構造体を使用します。
Atombufのデータ構造は次のようになっています。:
typedef struct atombuf { long a_argc; t_atom a_argv[1]; } t_atombuf, Atombuf;
配列 a_argv は a_argc で指定される可変の長さを持ちます。したがって、Atombuf x のサイズは sizeof(long) +x->argc * sizeof(t_atom) になります。
atombuf_new |
||
t_atomの配列から新規のAtombufを生成するために、atombuf_newを使用します。 | ||
t_atombuf *atombuf_new (long argc, t_atom *argv); | ||
argc | argv 配列の t_atomの数。たいていは0になります。 | |
argv | t_atomの配列。空のAtombufを作る場合には0を渡します。 | |
atombuf_newは、新しいAtombufを作り、それへのポインタを返します。0が返された場合、十分なメモリが得られなかったことを意味します。 |
atombuf_free |
||
Atombufに使用されていたメモリを解放する場合には、atombuf_freeを使用します。 | ||
void atombuf_free (t_atombuf *ab); | ||
ab | 解放されるAtombuf | |
Atombuf にはオブジェクトヘッダ情報が含まれていないため、freeobjectは使用できません。 |
atombuf_text |
||
テキストをAtombuf のt_atomに変換する場合に、atombuf_text を使用します。 | ||
void atombuf_text (t_atombuf **ab, char **buffer, long size); | ||
ab | 既存のatombuf変数へのポインタ。変数は変換されたテキストを持つ新しいAtombufによって置き換えられます。 | |
buffer | 変換されるテキストへのハンドル。0で終わる必要があります。 | |
size | テキストの文字数。 | |
テキストバッファから新しいAtombuf を作るためにこのルーチンを使用するには、最初に atombuf_new(0L,0L) を呼び出して、新規に空のt_atombuf を作ります。 |
ClockオブジェクトはMaxスケジューラへのインターフェイスになります。スケジューラを使用するには、インスタンス生成関数の中で clock_new を用いて、新しいClockオブジェクトを生成します。さらに、次に示すように、クロックが発せられたときに実行されるclock関数を書く必要があります。
void myobject_tick (myobject *x);
引数 x はclock_newへの引数 arg によって決まります。多くの場合、これはオブジェクト自身へのポインタとなります。
そして、メソッドのうちの1つに clock_delay または clock_fdelay を用いてスケジューリングを行います。スケジューリングを停止したい場合には、clock_unset を呼び出します。現在時刻を知るためには、gettime または clock_getftime を使います。Chapter9で述べられている setclock オブジェクトインターフェイスを用いることで、より高度なクロック操作が可能になります。より高い時間精度を持つ浮動小数点クロックルーチンの利用を推奨します。 metro などの標準的なMax4のタイミングオブジェクトはすべてこれらを使用しています。
ユーザがオーバードライブモード(Overdrive mode)をイネーブル(使用可)にした場合, あなたのクロック関数は割り込みレベルで実行されます。
clock_new |
||
新しいクロックオブジェクトを作成するために clock_new を使います。 | ||
Clock *clock_new (void *arg, method clockfun); | ||
arg | 呼び出された際に、クロック関数 clockfun に渡されるアーギュメント、ほとんどの場合、オブジェクト自身へのポインタになります。 | |
clockfun | クロックが発せられた際に呼び出される関数で、前述のように1つのアーギュメントをとる形で宣言されます。 | |
clock_new は新しく生成された clock オブジェクトへのポインタを返します。このオブジェクトはクロックが発せられた際にアーギュメント arg を渡し、関数 clockfun を実行します。通常 clock_new はインスタンス生成関数から呼び出され、割り込みレベルから呼び出すことはできません。作成した Clock オブジェクトを取り除く(解放する)ためには、freeobjectを使用します。 |
clock_delay |
||
クロックの実行をスケジューリングするために、clock_delay を使用します。 | ||
void clock_delay (Clock *cl, long interval); | ||
cl | スケジューリングするクロック | |
interval | クロックが実行されるまでのディレイ(遅延)、ミリセカンド単位。 | |
clock_delay は、現在の論理時刻から一定ミリセカンド後にクロックが発生するようセットします。 |
clock_fdelay |
||
clock_fdelay は浮動小数点アーギュメントを使ってクロックの実行をスケジューリングします。 | ||
void clock_fdelay(Clock *c, double time); | ||
c | スケジューリングするクロック | |
time | クロックが実行されるまでのディレイ(遅延)、ミリセカンド単位。 | |
clock_fdelay は clock_delay の浮動小数点バージョンです。 |
clock_unset |
||
スケジューリングされたクロックの実行をキャンセルする場合に、clock_unset を使います。 | ||
void clock_unset (Clock *cl); | ||
cl | キャンセルするクロック | |
clock_unset は渡されたクロックがセットされていない場合には何もしません。(報告もしません) |
gettime |
||
スケジューラの現在の論理時刻をミリセカンド単位で得るために、gettime を使います。 | ||
long gettime (void); |
clock_getftime |
||
浮動小数点アーギュメントとして現在の論理時刻を得るために、clock_getftime を使います。 | ||
void clock_getftime(double *time); | ||
time | 現在時刻を返します。 | |
clock_getftime は gettime の浮動小数点バージョンです。 |
通常の環境では、clock_delay や clock_fdelay を使ってスケジューリングを実行するために clock_gettime や clock_getftime は必要ありません。しかし、メッセージやイベントのタイミングを記録する場合には役に立ちます。
例として、Maxスケジューラを利用してメトロノームを書くための方法の一部を見てみましょう。まず、使用するデータ構造体は次のようになります。
typedef struct mymetro { t_object *m_ob; void *m_clock; double m_interval; void *m_outlet; } t_mymetro;
ここでは、クラスはすでに初期化されているものと仮定します。新しいクロックを割り当てるインスタンス生成関数は次のようになります。
void *mymetro_create (double defaultInterval) { t_mymetro *x; x = (t_mymetro *)newobject(mymetro_class); /* スペースの割り当て */ x->m_clock = clock_new(x, (method)mymetro_tick);/* クロックオブジェクトの生成*/ x->m_interval = defaultInterval; /* インターバルの保存 */ x->m_outlet = bangout(x); /* tickのためのアウトレット */ return x; /* 新しいオブジェクトを返す */ }
メトロノームをスタートさせるbangメッセージに応答するメソッドは次のようになります。
void mymetro_bang (t_mymetro *x) { clock_fdelay(x->m_clock,0.); }
次は、クロック関数です。
void mymetro_tick(t_mymetro *x) { clock_fdelay(x->m_clock, x->m_interval); /* 他のメトロノームtickのスケジュールリング */ outlet_bang(x->m_outlet); /* bang の送信 */ }
さらに、ある時点でメトロノームをストップさせる必要があるかもしれません。次は、メッセージstopに応答するために書かれたメソッドです。ここでは clock_unset を使っています。
void mymetro_stop (t_mymetro *x) { clock_unset(x->m_clock); }
オブジェクトのfree関数では、あなたが作ったClockを開放する freeojbject を呼び出す必要があります。
void mymetro_free (MyMetro *x) { freeobject((t_object *)x->m_clock); }
Systime API はスケジューラによる時間(前述の gettime を参照)の代わりに、システム時間を得るための手段を提供します。
systime_ticks |
||
オペレーションシステムの時間を tick で得るために、systime_ticks を使います。 | ||
unsigned long systime_ticks (void); |
||
システム時間を tick で返します。 。 |
systime_ms |
||
オペレーティングシステムの時間をミリセカンドで得るために、systime_ms を使います。 | ||
unsigned long systime_ms (void); |
||
システム時間をミリセカンドで返します |
systime_datetime |
||
オペレーティングシステムの日付、および時間を得るために、systime_datetime を使います。 | ||
unsigned long systime_datetime (t_datetime *d); |
||
システムの日付と時間を t_datetime データ構造体として返します。 typedef struct _datetime { unsigned long year; unsigned long month; unsigned long day; unsigned long hour; unsigned long minute; unsigned long second; unsigned long millisecond; // (将来の利用のために予約しています) } t_datetime; |
あなたのオブジェクトのメソッドは、割り込みレベルから呼び出されるかもしれません。これは、ユーザがOverDriveモードを有効(イネーブル)にし、あなたのメソッドのうちの1つが直接的に、あるいは間接的にスケジューラクロック関数から呼び出された場合に起こります。このことは、他のMaxオブジェクトから直接送られた任意のメッセージに応答するメソッド内において、特定の処理が確実に実行されるかどうか当てにならないということを意味します。そのような処理には、描画、ユーザへのオープンするファイルの問い合わせ、メモリの割り当てや開放を行うMacintosh Toolboxの割込みの呼び出しなどがあります。この制限を克服するために使用するメカニズムが、Qelem(queue element - キューエレメントの略)構造体です。Qelemはまた、プロセッサを集中的に使用するようなタスクを、割り込みレベルより低い優先順位で行います。例えば、スクリーンに描画をする場合、特にカラーであれば、MIDIデータを送信するようなタスクに比べて長い時間を要します。
QelemはClockと非常に良く似た動作を行います。qelem_new によって、生成関数の中で新しいQelemを作り、オブジェクトの中にそれへのポインタを保存します。そして、queue(キュー)関数を、clock関数と同様に書きます (queue 関数は clock関数と同様に1つの引数を取りますが、たいていの場合、これはオブジェクトへ自身へのポインタです)。この関数はQelemがセットされた際に呼び出されます。qelem_set を呼び出してQelemをセットすると、この関数が実行できるようになります。
QelemとClockを同時に使いたいと思うことも少なくないでしょう。例えば、毎秒20回変化するカウンタのためにディスプレイを更新したいとします。これは、次のようにして達成できます。qelem_set を呼び出すClock関数を書き、前述のメトロノームの例で使ったテクニックによって自分自身を50ミリセカンド後に再スケジュールするようにするのです。この方法では、たとえコンピュータがカウンタを描画し終えるより速く qelem_set を呼び出したとしてもうまく働きます。それは、Qelemがすでにセットされていた場合には、qelem_setは再びセットを行なわないからです。しかし、コンピュータがカウンタを描画したときに表示されるのはClock関数で生成された特定の値ではなく、現在の値になります。
この章でこれから述べるQelemに基づいた defer (後回し)のメカニズムによって、read メッセージに応答して標準ファイルダイアログボックスを開くような1回きりのイベントの優先度を簡単に下げることができるという点に注意して下さい。
あなたのQelemルーティンが outlet_int や他のアウトレット関数を使ってメッセージを送信する場合、後述の「割り込みレベルに関する注意事項(Interrupt Level Considerations)セクションで述べるロックアウト( lockout) メカニズムを使用する必要があります。
qelem_new |
||
新しいQelemを作るために、qelem_new を使います。 | ||
Qelem *qelem_new (void *arg, method fun); | ||
arg | Qelemが実行される際に、関数funに渡される引数。通常はオブジェクト自身へのポインタになります。 | |
fun | 実行される関数 。 | |
あらゆる種類の描画や、メモリの割り当て・開放を行うMacintosh Toolboxルーティンの呼び出しは、Qelem関数の中で行なわなければなりません。qelem_newの戻り値は、qelem_setに渡すために保存しておく必要があります。 Qelemを開放するためには、freeobject を呼び出すのではなく、代りに qelem_freeを使う点に注意して下さい。 |
qelem_set |
||
Qelemを実際に実行するためには、qelem_set を使います。 | ||
void qelem_set (Qelem *qe); | ||
qe | メインレベルで関数が実行されるQelem 。 | |
qelem_set のキーとなる動作は次のようなものです。:Qelem オブジェクトがすでにセットされている場合、再びセットはされません。(そうしたくない場合は、後述のdefer を参照して下さい)これは、「データが変更された場合にそれを再描画したいが、描画が終わるより速く生じる変化には応答してほしくない」という場合に効果的です。Qelemオブジェクトはキュー関数が呼び出されると、セットを解除します。 |
qelem_unset |
||
Qelemの実行をキャンセルしたい場合に、qelem_unset を使います。 | ||
void qelem_unset (Qelem *qe); | ||
qe | 実行をキャンセルしたいQelem。 | |
Qelemの関数が呼び出しのためにセットされている場合、qelem_unset はその呼び出しをストップします。そうでない場合は何もしません。 |
qelem_front |
||
高い優先度でQelemを実行させたい場合、qelem_front を使います。 | ||
void qelem_front (Qelem *qe); | ||
qe | メインレベルで実行される関数を持つQelem 。 | |
この関数は qelem_set と同様なものですが、Qelem の関数をメインイベントレベルにある実行ルーティンのリストの最後ではなく先頭に置きます。qelem_front は特に時間に関して厳密を要するアプリケーションでのみ、注意して使用するようにして下さい。 |
qelem_free |
||
オブジェクトのfree関数の中でQelemを破棄する場合に、qelem_free を使用します。 | ||
void qelem_free (Qelem *qe); | ||
qe | 破棄されるQelem 。 | |
この関数は、すでに割り当てられているQelemオブジェクトを破棄します。Qelemによって使用されているメモリを解放する場合には、freeobject の代りにこの関数を使います。 |
あなたのオブジェクトは、割り込みレベルのメッセージに応答するかもしれません。そのため、オブジェクトが正しく動作するためには、メソッドを書く際に守るべきいくつかのガイドラインがあります。割り込みレベルの処理はユーザがOptionsメニューのOverdriveを選択した場合に有効(イネーブル)になります。割り込みレベル処理の有利な点は、タイミングの正確さの向上、ユーザがメニューバーを使用したりウィンドウをドラッグする際にMaxパッチがスムーズに処理を続ける能力の向上、遅いスクリーン描画に対して時間の正確さを求められるclockやシリアルポート操作の優先順位を高くする能力の向上です。
MacOS9上で動作するMax/MSP の古いバージョンでは、ハードウェア割り込みモデルを使っていました。しかし、Max4.2 以降では、Winodows XP、Mac OSX の両方とも、Overdrive やオーディオ処理に関連したソフトウェア割り込みモデルが使われています。言い換えれば、様々なタスクのために、個別のスレッドを使うということです。この新しいマルチスレッドモデルでは、スレッドの優先度に関わらす、オペレーティングシステムが適当と考えれば、優先度の低いスレッドが優先度の高いスケジューラやオーディオスレッドに対して割り込みをかけることができます。これはOS9環境下では、ありえなかったことです。OS9では、低い優先度のスレッドは決してスケジューラ割り込みやオーディオ割り込みに対して割り込みをかけることができず、スケジューラ割り込みは決してオーディオ割り込みに対して割り込みをかけることができませんでした。もし、あなたのコードが前記のようなOS9の性質に依存しているのであれば、おそらくコードの書き方について再点検する必要があるでしょう(実際、私たちもそうせざるを得ませんでした)。
割り込みレベル処理の基本的なルールは次のようなものです。
割り込みによる処理の問題を扱う、いくつかの付加的なルーチンがあります。
isr |
||
コードがMax のタイマ割り込みの中で実行されるかどうかを決定するために、isr を使います。 | ||
short isr(void); | ||
この関数は、Max のタイマ割り込み内の場合は0でない値を返し、そうでない場合は0を返します。あなたのコードが、デバイスドライバで使用される同期モードのような別のタイプの割り込みレベルのコールバックをセットする場合、isr がfalseを返す点に注意して下さい。 |
defer |
||
関数が割り込みレベルで実行されるとき(およびその場合に限り)、メインレベルで関数の実行を遅れさせたい場合に defer を使います。 | ||
void defer (t_object *client, method fun, t_symbol *s, short argc, t_atom *argv); | ||
client | 最初の引数は、関数funの実行時にfunに渡されます。 | |
fun | 呼び出される関数。どのように宣言するかは下記の文を参照して下さい。 | |
s | 関数funが実行される時に渡される2番目の引数。 | |
argc | argv の引数の数。argcはまた、関数funが実行される時に渡される第3の引数でもあります。 | |
argv | 関数への引数となる変数を含む配列。この引数が0でない場合、 defer は引数のコピーを作るために(argc によって渡されたサイズによって)メモリを割り当て、関数funが実行される際に、その第4引数としてこの配列のコピーを渡します。 | |
この関数は、isrルーチンを使って、Maxのタイマ割り込みレベルで実行されているかどうかを決定します。Maxのタイマ割り込みレベルである場合、deferはQelemを作り、qelem_front を呼び出し、そのqueue関数が、渡された関数funを、指定された引数と共に呼び出します。Maxのタイマ割り込みレベルでない場合、関数は引数が渡されるとた直ちに実行されます。このことは、デバイスやファイルマネージャI/0完了ルーティンなどが使われる状況では、deferの使用は適切でないということを意味しているため注意が必要です。しかし、次に述べる defer_lowは、どんな状況ででも「待っている」ため、こういった場合の使用にも適しています。待たされる関数は次のように宣言する必要があります。: void myobject_do (myObject *client, t_symbol *s, short argc, t_atom *argv); |
defer_low |
||
メインレベルで関数の実行を遅れさせる場合に、defer_lowを使います。 | ||
void defer_low (t_object *client, method fun, t_symbol *s, short argc, t_atom *argv); | ||
client | 関数funを実行する際に渡される第1の引数 。 | |
fun | 呼び出される関数。どのように宣言するかは下記の文を参照して下さい。 | |
s | 関数funを実行する時に渡される第2の引数 | |
argc | argvのアーギュメントの数。argcは関数funを実行する際に渡される第3の引数でもあります。 | |
argv | 関数への引数となる変数を含む配列。このアーギュメントが0でない場合、 defer_lowはアーギュメントのコピーを作るために(argcで渡されたサイズに基づいて)メモリの割当てを行い、そのコピーされた配列を関数funを実行する際に第4の引数として渡します。 | |
defer_lowは割り込みレベルであるかどうかに関わらず、また、qelem_set を使うか qelem_front を使うかに関わらず、関数funの呼び出しを常に遅れさせます。この関数は、ファイルの書き込み・読み出し等の場合のように、オブジェクトがダイアログを開いたために生じるメッセージに対応する場合に使用することをお勧めします。 |
schedule |
||
将来の、ある時点で、タイマレベルで関数を実行する場合、scheduleを使用します。 | ||
void schedule (t_object *client, method fun, long time, t_symbol *sel, short argc, t_atom *argv); | ||
client | 関数funが実行される時に渡される第1の引数。これは、オブジェクト自身へのポインタであるという規約になっています。 | |
fun | 実行される関数。宣言のしかたについては下記を参照。この関数は割込みレベルで呼び出されます。 | |
time | 関数が実行される論理時刻。 | |
sel | 関数funに渡される第2の引数。 | |
argc | argvに含まれるAtomの数。; 関数funへの第3の引数。 | |
argv | 関数funへの他の引数。何もない場合は0L。 | |
scheduleは将来のある時点で関数を呼びだします。deferと異なり、関数は論理時刻がtimeで指定された値と等しくなったときにスケジューリングループから呼び出されます。このことは、関数が割り込みレベルで呼び出される可能性があるということを意味しているため、通常の割り込みレベル管理のための制約に従わなければなりません。scheduleに渡される関数funは次のように宣言する必要があります。 void myobject_do (myObject *client,t_symbol *s, short argc, t_atom *argv); scheduleの1つの使い方として、lockoutフラグの代替があります。これは、lockout_setの呼びだしに挟まれたoutlet_intの代わりに、scheduleを呼び出すclickメソッドの例です。 訳注:次の schedule_delay の最後に、この例があります。 |
schedule_delay |
||
ディレイオフセットで指定された先の時点にタイマレベルで関数を実行する場合には、schedule_delay を使います。 | ||
void schedule (t_object *client, method fun, long delay, t_symbol *sel, short argc, t_atom *argv); | ||
client | 関数funが実行される時に渡される第1の引数。これは、オブジェクト自身へのポインタであるという規約になっています。 | |
fun | 実行される関数。宣言のしかたについては下記を参照。この関数は割込みレベルで呼び出されます。 | |
delay | 現在時刻から関数が実行されるまでのディレイ(遅延)。 | |
sel | 関数funに渡される第2の引数。 | |
argc | argv に含まれるAtomの数。; 関数funへの第3の引数。 | |
argv | 関数funへの他のアーギュメント。何もない場合は0L。 | |
schedule_delayはscheduleと同等のものですが、時間を、指定した論理時刻ではなく、delayとして指定することができる点が異なります。 schefuleやschedule_delayの1つの使い方として、lockoutフラグの代替があります。ここでは、lockout_setの呼び出しに挟まれたoutlet_intの代わりに、scheduleを呼び出すclickメソッドの例を揚げます。 void myobject_click (t_myobject *x, Point pt, short modifiers) { t_atom a[1]; a[0].a_type = A_LONG; a[0].a_w.w_long = Random(); schedule_delay(x, myobject_sched, 0 ,0, 1, a); } void myobject_sched (t_myobject *x, t_symbol *s, short ac, t_atom *av) { outlet_int(x->m_out,av->a_w.w_long); } |
Max4.1 以前のバージョンでは、"lockout" と呼ばれるシンプルな保護メカニズムがありました。これは、アウトレットからのデータ送信や、リンクされたリストのメンバの変更のような微妙な処理の間、スケジューラが優先度の低いスレッドに割り込むのを防ぐものでした。この lockout メカニズムは廃止され、MacOSX や Windows XP バージョン(Max 4.2 以降)では、何も行なわれません。それでは、どのようにしてこのような微妙な動作のスレッドを保護するのでしょうか?そのような場合にはクリティカルリージョン(別名、クリティカルセクション)を使って下さい。しかし、これは非常に重要なことですが、すべてのアウトレット呼び出しは現在では安全なスレッドであり、クリティカルリージョンに含めるべきではありません。もしそうでなければ、これは深刻なタイミングの問題となっていたでしょう。リンクされたリストへのアクセスのように、スレッドが保護されないそれ以外のタスクの場合には、クリティカルリージョンや他のスレッド保護メカニズムが適切です。
クリティカルリージョンはシンプルなメカニズムです。これは、複数のスレッドが同じクリティカルリージョンとして保護されているコードに同時にアクセスすることを防ぎます。コードフラグメントは異なる可能性があり、、これはまったく別のモジュールになります。しかし、クリティカルリージョンが同じである限り、2つのスレッドが保護されたコードを同時に呼び出すことはできません。1つのスレッドがクリティカルリージョン内にあり、他のスレッドが同じクリティカルリージョンで保護されたコードを実行したい場合には、2番目のスレッドは最初のスレッドがクリティカルリージョンを出るまで待たなければなりません。いくつかの実装例では、最初のスレッドに時間がかかりすぎるとクリティカルリージョンを出るように設定し、2番目のスレッドがこれを実行することができますが、これは、危険で、潜在的にクラッシュを引き起こす可能性があります。これが、Maxによってクリティカルリージョンが危険にさらされるケースです。スレッドがクリティカルリージョンにとどまることを許される時間の上限はデフォルトで2秒になっています。2つのスレッドがクリティカルリージョンで危険な状態に陥るまでに2秒の余裕が与えられているとはいえ、クリティカルリージョンによる保護をコードのできるだけ小さい部分に対してのみ行なうことは重要です。
Max では、クリティカルリージョンに入るために、critical_enter 関数を使います。また、クリティカルリージョンを出る場合には critical_exit 関数を使います。クリティカルリージョンを使用する関数はすべて、クリティカルリージョンを出るまで、すべてのコントロールパスが保護されるということは重要です(goto文、return文には注意して下さい)。 critical_enter および critical_exit 関数は、クリティカルリージョンを引数として取ります。しかし、ほとんどすべての用途で、グローバルクリティカルリージョンを使うことを推奨します。その場合、この引数の値は0になります。複数のクリティカルリージョンの使用は、デッドロックのような問題を引き起こす可能性があります。すなわち、スレッド #1 がクリティカルリージョン A の中にあってクリティカルリージョン B を待っているが、スレッド #2 がクリティカルリージョン B の中にあって、クリティカルリージョン A を待っているといった場合です。Max のようにフレキシブルなプログラミング環境では、デッドロックの状態はあなたが思うより容易に生じてしまいます。そのため、行なっていることが完全に大丈夫という確信が持て、なおかつコードの保護に複数のクリティカルリージョンが絶対必要であるという場合以外は、グローバルなクリティカルリージョンを使うことを推奨します。
次のサンプルコードでは、リンクされたリストの横断を保護するためにクリティカルリージョンを使う例を示しています。これは、リストの最初の要素が "val" と同じ値であるものを見つけるためにテストするものです。このコードが保護されていなければ、リンクされたリストを変更する他のスレッドによって、横断コードの中での条件が無効になってしまう可能性があります。
critical_enter(0); for (p = head; p; p = p->next) { if(p->value == val) break; } critical_exit(0); return p;
さらに、複数のコントロールパスがクリティカルリージョンで保護されている場合、確実にクリティカルリージョンを出る方法の実例を挙げておきます。次のコードは、少し修正を加えたものです。
critical_enter(0); for (p = head; p; p = p->next) { if (p->value == val) { critical_exit(0); return p; } } critical_exit(0); return NULL;
マルチスレッドプログラミング、ハードウェア割り込み、それに関連したトピックスなどに関するより詳しい情報を得るためには、オンラインで調べるか、「Modern Operating Systems」(Andrew S Tanenbaum; Prentice Hall) の関連した章を読むことをおすすめします。コードを書きながらであれば、この本の関連した章のいくつかは PDF フォーマットとして Prentice Hall のウェブサイトからダウンロードできます。「サンプルセクション」を参照して下さい。
www.prenhall.com/divisions/esm/app/author_tanenbaum/custom/mos2e/
critical_new |
||
新しいクリティカルリージョンを作成する場合に、critical_new を使います。 | ||
void critical_new (t_critical *cr); | ||
cr | このポインタによって、t_critical 構造体が返されます。 | |
Max のグローバルクリティカルリージョンが使えるので、通常はオブジェクト自身のクリティカルリージョンを作成する必要はありません。グローバルクリティカルリージョンが使えないことが確実である場合にのみ、(オブジェクトのインスタンス生成メソッドの中で)この関数を使います。 |
critical_free |
||
critical_new によって作られたクリティカルリージョンを開放する場合に、critical_free を使います。 | ||
void critical_free (t_critical cr); | ||
cr | 開放される t_critical構造体 。 | |
独自のクリティカルリージョンを作成した場合には、オブジェクトの free メソッドでこれを開放する必要があります。 |
critical_enter |
||
クリティカルリージョンに入る場合には、critical_enter を使います。 | ||
void critical_enter (t_critical cr); | ||
cr | t_critical 構造体へのポインタ。0ならば Max のグローバルクリティカルリージョンを使用します。 | |
通常は、グローバルクリティカルリージョンに入るために使用するので、引数は0になるでしょう。しかし、critical_new で作成した独自のクリティカルを渡すことも可能です。クリティカルリージョン内のコードの量を最小限にとどめようとすることが重要です。 |
critical_exit |
||
クリティカルリージョンを出る場合には、critical_exit 使います。 | ||
void critical_exit (t_critical cr); | ||
cr | t_critical 構造体へのポインタ。0ならば Max のグローバルクリティカルリージョンを使用します。 | |
通常は、グローバルクリティカルリージョンから出るために使用するので、引数は0になるでしょう。しかし、独自のクリティカルリージョンを使用している場合には、critical_enter に渡したものと同じポインタを渡す必要があります。 |