2010年5月19日水曜日

HOWTO write an External

(暫定版)

以下は HOWTO writen an External 英語版を個人的に利用するために勝手に翻訳したものです。
原文の注釈:ボールド、アスタリスク + 数字
翻訳者の注釈:角括弧 [ ] で囲まれた文字列

1 definitions and prerequisites
pd とは Miller S. Puckette によるグラフィカルな、リアルタイム・コンピュータ・ミュージック・プログラム puredata をさします。
このドキュメントを完全に理解するために、pd と一般的なプログラミング技術の理解、とりわけ C に精通している必要があります。
external を自分で書くために、linux システムの Gnu C-Compiler ( gcc ) や Windows プラットフォームの Visual-C++ 6.0 ( vc6 ) のような ANSI C をサポートする C コンパイラが必要でしょう。


1.1 classes, instances, objects
pd は C言語で書かれています。そのグラフィカルな特徴から、pd はオブジェクト指向システムです。残念ながら C はクラスの使用を十分にサポートしていません。従って、ソースコードは C++ で書かれたもののようにすっきりとしていません。
このドキュメントで、クラスという語句はデータと、そのデータに関する操作を組み合わせた概念の実装をさします。
クラスの具体的なインスタンスをオブジェクトと呼びます。

1.2 internals, externals und libraries
混乱を避けるために internal、external、library という語句をここで説明しましょう。

Internal  internal は pd に組込まれたクラスです。+, pack, sig~ のような、たくさんのものが internal です。


External  external は pd に組込まれていませんが、実行時にロードされます。いったん pd のメモリにロードされたら、external は internal と区別することはできません。

Library  ライブラリは単独のバイナリファイルへとコンパイルされた external のコレクションです。
ライブラリはシステムに依存する命名規則に従わなければなりません:[ MacOS の場合は my_lib.pd_darwin ]
library linux irix Win32

my_lib my_lib.pd_linux my_lib.pd_irix my_lib.dll
最も単純なライブラリの形式は、ライブラリと同じ名前をもつ external をちょうど1つを含むものです。
external と異なり、pd はライブラリを特別な操作でインポートすることができます。ライブラリがインポートされたら、含まれる全ての external はメモリにロードされ、オブジェクトとして使うことができます。
pd はライブラリをインポートする方法をサポートします:

・コマンドラインのオプション経由 -lib my_lib
・オブジェクトの作成による my_lib

最初の方法は pd が起動したときにライブラリをロードします。この方法は、いくつか external を含んだライブラリに好んで使われます。
もうひとつの方法は、ライブラリと同じ名前をもつ external をちょうど1つを含むライブラリに使われるべきです。pd は my_lib という名前のクラスが既にロードされているかどうか、まず最初にチェックします。ロードされていない場合*1、全てのパスから my_lib.pd_linux *2 というファイルを調べます。見つかったなら、含まれる全ての external は my_lib_setup() ルーチンを呼ぶことによって、メモリにロードされます。ロード後、(新たにロードされた)exteral は(再び)my_lib クラスを探します。 成功したら、インスタンスは生成され、それ以外はインスタンス化に失敗し、エラーが出力されます。いずれの方法でも、ライブラリのなかで宣言された external のクラス群はすべてロードされます。
----------------------------------------------
*1  my_lib クラスが既にロードされている場合は、my_lib オブジェクトがインスタンス化されて、手順は完了します。従って、ライブラリはロードされません。例えばクラス名が abs のような、既に使われている名前で命名されたライブラリはロードされません。

*2   または他のシステムに依存するライブラリの拡張子。
----------------------------------------------

2 my first external: helloworld
プログラミング言語のはじめての学習は大抵 hello world アプリケーションです。
はじめての external の場合、bang メッセージをトリガとし、標準エラーに hello world!! を出力するオブジェクトのクラスを作成するのがよいでしょう。

2.1 the interface to pd
pd の external に明確に定義されたインターフェイスを書くことは必要とされます。インターフェイスはヘッダファイル m_pd.h が提供します。


#include "m_pd.h"

2.2 a class and its dataspace
はじめに新しいクラスを準備し、このクラスのためのデータ領域を定義しなければなりません。

static t_class *helloworld_class;

typedef struct _helloworld {
t_object x_obj;

} t_helloworld;

helloworld_class は新しいクラスへのポインタになります。
( _helloworld型の ) t_helloworld 構造体はクラスのデータ領域です。
データ領域に必須の要素は t_object型の変数です。t_object型の変数は、オブジェクトのグラフィカル表現でのインレットとアウトレットについてのデータのような、内部のオブジェクトのプロパティを格納するのに使用されます。
t_object は構造体の一番最初にエントリしてなければなりません!
単純な hello world アプリケーションは変数を必要としないため、この構造体は t_object 以外はなにもありません。

2.3 methodspace
データ領域を除いて、クラスは格納するデータを操作するための操作子(メソッド)一式が必要です。

クラスのインスタンスにメッセージが送信される場合、メソッドが呼出されます。これらのメソッドは pd のメッセージシステムへのインターフェイスです。原則として、メソッドは戻り値をもたないので、void型です。
----------------------------------
英語原文:If a message is sent to an instance of our class, a method is called. These methods are the interface to the messagesystem of pd. On principal they have no return argument and are therefore are of the type void.
独語原文:Wird eine Message an eine Instanz unserer Klasse geschickt, so wird eine Methoden aufgerufen. Diese Methoden, die die Schnittstelle zum Messagesystem von pd bilden, haben grundsätzlich kein Rückgabeargument, Sind also vom Typ void.
メッセージが私たちのクラスのインスタンスに送信される場合、このメソッドが呼ばれます。pd のメッセージシステムへのインターフェイスを提供するこれらのメソッドは基本的には戻り値の引数をもっておらず、従って、void型です。

----------------------------------
void helloworld_bang(t_helloworld *x)
{

post("Hell world!!");
}

このメソッドはデータ領域を操作できるようになる t_helloworld型の引数をもっています。
ここではただ単に Hello world!! を出力したいと思っているので( ついでながら、書いたデータ領域は非常に少ない )、データ領域操作の記述はありません。
コマンド post(char *c, ...) は標準エラーに文字列を送信します。復帰 [ 原文:carriage return ] は自動的に追加されます。この点を除いて、 post コマンドは C のコマンド printf() のように動作します。

2.4 generation of a new class
新しいクラスを生成するために、このクラスのデータ領域とメソッド領域の情報は、ライブラリがロードされるときに pd に渡されなければなりません。
新しい my_lib ライブラリをロード中、pd は my_lib_setup() 関数を呼出そうと試みます。この関数(あるいはセットアップ関数によって呼出された関数)は新たなクラスとそのクラスのプロパティを宣言します。セットアップ関数は、ライブラリがロードされるときに一度だけ呼出されます。(例えば、指定する名前の関数が存在しないからといった理由で)もし関数呼出しが失敗したならば、ライブラリの external はロードされないでしょう。


void helloworld_setup (void)
{
helloworld_class = class_new( gensym( "helloworld" ),
( t_newmethod )helloworld_new,
0, sizeof( t_helloworld ),
CLASS_DEFAULT, 0);

class_addbang( helloworld_class, helloworld_bang );
}


class_new
class_new 関数は新たなクラスを作成し、この原型となる新しいクラスにポインタを返します。
最初の引数はクラスの名前を表す文字列です。
次に続く2つの引数はクラスのコンストラクタとデストラクタを定義します。
pd パッチ内でクラスオブジェクトが作成されるときは必ず、クラスのコンストラクタ ( t_newmethod ) helloworld_new はオブジェクトをインスタンス化してデータ領域を初期化します。
オブジェクトが破棄されるときは必ず、( 含んでいるパッチを閉じることや、パッチからオブジェクトの削除、どちらも )デストラクタは動的に予約されたメモリを解放します。スタティックなデータ領域のために確保されたメモリは自動的に予約、解放が行なわれます。
従って、この例ではデストラクタを提供すべきではなく、引数は 0 に設定します。
pd にスタティックなデータ領域のための十分なメモリを予約、解放できるようにするために、[ 第4番目の引数として t_helloworld型の ] データ構造体のサイズが渡されなければなりません。

第5番目の引数は、クラスのオブジェクトのグラフィカルな表現に影響しています。デフォルトの値は CLASS_DEFAULT で単に 0 です。
残りの引数はオブジェクトの引数と型の定義です。
A_DEFFLOAT と A_DEFSYMBOL を通して、数値かオブジェクトの引数を表す文字列で最大6つまで定義できます。より多くの引数を、より柔軟な方法でにアトムの型の順序をオブジェクトに渡したいときは、A_GIMME を任意のアトムのリストを渡すために使うことができます。[ 原文:If more arguments are to be passed to the object or if the order of atomtype should by more flexible, A_GIMME can be used for passing an arbitrary list of atoms. ]
オブジェクトの引数のリストは0で終わらせます。この例で、クラスのオブジェクトの引数は他にもうありません。

class_addbang
まだクラスのメソッド領域に付け加えなければなりません。class_addbang は第1番目の引数に定義されたクラスに bang メッセージのためのメソッドを加えます。追加されたメソッドは第2番目の引数に定義します。

2.5 constructor: instantiation of an object
オブジェクトが pd パッチ内で作成されるたびに、class_new のコマンドで定義されたコンストラクタはクラスの新しいインスタンスを生成します。

コンストラクタは void型のポインタでなければいけません。

void *helloworld_new(void)
{
t_helloworld *x = (t_helloworld *)pd_new( helloworld_class );

return (void *)x;
}

コンストラクタの引数は class_new で定義されたオブジェクトの引数に依存します。

class_new の引数   コンストラクタの引数
A_DEFFLOAT     t_floatarg f
A_DEFSYMBOL    t_symbol *s
A_GIMME       t_symbol *s, int argc, t_atom *argv
helloworld クラスのオブジェクトの引数はないので、ここでのコンストラクタにもありません。
関数 pd_new がデータ領域のためのメモリを予約し、オブジェクトの内部変数を初期化して、データ領域へのポインタを返します。
データ領域型へのキャストが必要です。
通常、コンストラクタはオブジェクトの変数を初期化するでしょう。しかしながら、ここでは変数がないので、その必要はありません。
コンストラクタはデータ領域へのポインタを返さなければなりません。


2.6 the code: helloworld
#include "m_pd.h"

static t_class *helloworld_class;

typedef struct _helloworld {
t_object x_obj;
} t_helloworld;


void helloworld_bang(t_helloworld *x)
{
post("Hello world !!");
}

void *helloworld_new(void)
{
t_helloworld *x = (t_helloworld *)pd_new(helloworld_class);


return (void *)x;
}

void helloworld_setup(void) {
helloworld_class = class_new(gensym("helloworld"),
(t_newmethod)helloworld_new,
0, sizeof(t_helloworld),
CLASS_DEFAULT, 0);
class_addbang(helloworld_class, helloworld_bang);

}

3 a simple external: counter
単純なカウンタを exernal として実装したいと思います。bang をトリガに、カウンタの値をアウトレットに出力し、その後カウンタの値を1増加させます。
このクラスは前章で作成したものに似ていますが、データ領域は変数 counter によって拡張され、結果は、標準エラーへの文字列の出力の代わりに、アウトレットへのメッセージとして書出されます。

3.1 object-variables
もちろん、カウンタは実際のカウンタの値を格納するための状態変数が必要です。
クラスのインスタンスに属する状態変数はデータ領域に属します。


typedef struct _counter {
t_object x_obj;
t_int i_count;
} t_counter;

整数の変数 i_count はカウンタの値を格納します。

3.2 object-arguments
ユーザが初期値を定義できるのなら、カウンタはずっと便利になります。したがって、この初期値は作成時にオブジェクトに渡されるようにするべきでしょう。


void counter_setup(void) {
counter_class = class_new(gensym("counter"),
(t_method)counter_new,
0, sizeof(t_counter),
CLASS_DEFAULT,
A_DEFFLOAT, 0);

class_addbang(counter_class, counter_bang);
}


関数 class_new(:A_DEFFLAOT は pd に知らせます。)に追加の引数があるので、オブジェクトは t_floatarg型の引数が1つ必要です。渡すがもうない場合、0 をデフォルトにします。

3.3 constructor
コンストラクタはいくつか新たなタスクをもちます。変数の値は初期化されなければならない一方、他方で、オブジェクトのためのアウトレットは作成されなければなりません。

void *conter_new(t_floatarg f)
{
t_counter *x = (t_counter *)pd_new(counter_class);

x->i_count=f;

outlet_new(&x->x_obj, &s_float);

return (void *)x;
}

このコンストラクタメソッドは、セットアップルーチンの中の class_new によって宣言された1つのt_floatarg型の引数をもっています。この引数はカウンタを初期化するのに使われます。
関数 outlet_new をもって、新しいアウトレットは作成されます。第1番目の引数は新たにアウトレットが作成されるためのオブジェクトの内部へのポインタです。第2番目の引数はアウトレットの型を表す文字列の記述 [ 原文:a symbolic description of the outlet-type. ] です。カウンタは数値を出力すべきなので、float型です。
outlet_new は、新しいアウトレットへのポインタを返し、t_object型変数の x_obj.ob_outlet にポインタを保存します。1つだけアウトレットを使用する場合、ポインタはさらにデータ領域に格納される必要はありません。t_object型変数はたったひとつのアウトレットポインタしか保持できないので、ひとつ以上のアウトレットを使用する場合、ポインタをデータ領域に格納しなければなりません。


3.4 the countermethod
トリガを機にカウンタの値をアウトレットに送信し、その後、インクリメントさせます。

void counter_bang(t_counter *x)
{
t_float f = x->i_count;
x->i_count++;
outlet_float(x->xobj.ob_outlet, f);

}

関数 outlet_float は浮動小数点値(第2番目の引数)を第1番目の引数によって特定されるアウトレットに送信します。
最初に浮動小数点バッファのなかにカウンタを格納します。その後、カウンタがインクリメントされ、それより前にはどんなバッファの変数もアウトレットに送信されません。
一見して無益なように思われることですが、更なる点検の後、理解してください:バッファの変数は t_float型として実装されており、outlet_float は浮動小数点値を予測しているので、型のキャストは避けられません。
もしカウンタの値がインクリメントされる前にアウトレットに送信されてしまったならば、これは、結果として望まれない(明確に定義したにもかかわらず)動作をひきおこすでしょう:もしカウンタのアウトレットが自身のインレットを直接トリガするならば、カウンタの値がまだインクリメントされていないにもかかわらず、カウンタのメソッドは呼出されるでしょう。通常、これは望んでいることではありません。
同じ(正しい)結果はもちろん、1行で得ることができますが、リエントラントの問題を不明瞭にします。

3.5 the code: counter
#include "m_pd.h"


static t_class *counter_class;

typedef struct _counter {
t_object x_obj;
t_int i_count;
} t_counter;

void counter_bang(t_counter *x)

{
t_float f = x->i_count;
x->i_count++;
outlet_float(x->x_obj.ob_outlet, f);
}

void *counter_new(t_floatarg f)
{

t_counter *x = (t_counter *)pd_new(counter_class);

x->i_count = f;
outlet_new(&x->x_obj, &s_float);

return (void *)x;
}


void counter_setup(void) {
counter_class = class_new(gensym("counter"),
(t_method)counter_new,
0, sizeof(t_counter),
CLASS_DEFAULT,
A_DEFFLOAT, 0);

class_addbang(counter_class, counter_bang);
}


4 a complex external: counter
前章のシンプルなカウンタは容易にもっと複雑に拡張することができます。カウンタの初期値がリセット、上限、下限を設定、ステップ幅の制御ができることはとても便利かもしてません。それぞれの超過は bang メッセージを第2番目のアウトレットに送って、カウンタを初期値にリセットするようにしましょう。

4.1 extended dataspace
typedef struct _counter {
t_object x_obj;
t_int i_count;

t_float step;
t_int i_down, i_up;
t_outlet *f_out, *b_out;
} t_counter;

データ領域はステップ幅、上限、下限のための変数を保持するために拡張されています。そのうえ2つのアウトレットのためのポインタが追加されています。

4.2 extension of the class

新たなクラスオブジェクトは set、reset のような、異なるメッセージのためのメソッドをもっていたほうがいいでしょう。従って、メソッド領域も拡張されなければなりません。

counter_class = class_new(gensym("counter").
(t_method)counter_new,
0, sizeof(t_counter),
CLASS_DEFAULT,
A_GIMME, 0);

クラスのジェネレータ class_new は A_GIMME によって拡張されています。これは、オブジェクトのインスタンス生成時に、動的に引数の個数を渡すことを可能にします。


class_addmethod(counter_class,
(t_method)counter_reset,
gensym("reset"), 0);

class_addmethod はクラスに任意のセレクタのためのメソッドを加えます。
第1番目の引数はメソッド(第2番目の引数)が追加されるクラスです。
第3番目の引数はメソッドに関係づけられるべき、セレクタを表す文字列です。
残りの0で終了している引数は、セレクタに従うアトムのリストを記述しています。

class_addmethod(counter_class,

(t_method)counter_set, gensym("set"),
A_DEFFLOAT, 0);
class_addmethod(counter_class,
(t_method)counter_bound, gensym("bound"),
A_DEFFLOAT, A_DEFFLOAT, 0);

数値1つに従った set メソッド、同様に数値2つに従った bound セレクタ メソッドが加えられています。

class_sethelpsymbol (counter_class, gensym("help-counter"));


もし pd のオブジェクトが右クリックされた場合、オブジェクトのクラスを記述したヘルプパッチを開くことができます。デフォルトでは、このパッチの場所は、doc/5.reference/ の中にあり、クラスの名前を表す文字列のように名付けられます。
かわりのヘヘルプパッチは class_sethelpsymbol コマンドで定義できます。

4.3 construction of in- and outlets
オブジェクトを作成するととき、数種類の引数が、ユーザによって渡されるかも知れません。

void *counter_new(t_smbol *s, int argc, t_atom *argv)

A_GIMME を伴う関数 class_new 内の引数の宣言により、コンストラクタは以下の引数をもっています:
t_symbol *s   オブジェクト作成時に使用される、名称を表す文字列。

int argc     オブジェクトに渡した引数の個数。
t_atom *argv  argc個のアトムのリストのためのポインタ。[ 原文:a pointer to a list of argc atoms ]


t_float f1 = 0, f2 = 0;

x->step = 1;

switch (argc) {
default:

case 3:
x->step = atom_getfloat(argv+2);
case 2:
f2 = atom_getfloat(argv+1);
case 1:
f1 = atom_float(argv);
break;
case 0:

}
if (argc<2) f2 = f1;
x->i_down = (f1<f2)?f1:f2;
x->i_up = (f1>f2)?f1:f2;

x->i_count = x->i_down;


3つの引数が渡される場合、それらは、下限、上限、ステップ幅となります。
もし2つのみ引数が渡される場合、ステップ幅はデフォルトの1となります。もし1つのみ引数が渡される場合、1のステップ幅を伴うカウンタの初期値となります。

inlet_new(&x->x_obj, &x->x_obj.ob_pd,
gensym("list"), gensym("bound"));

関数 inlet_new は新しいアクティブなインレットを作成します。アクティブとは、メッセージがアクティブなインレットに送信されるたびに、クラスメソッドが呼出されるということです。

[ pdの ]ソフトウェアアーキテクチャのため、第1番目のインレットは常にアクティブです。
関数 inlet_new の最初の2つの引数は、オブジェクトの内部と、オブジェクトのグラフィカル表現のためのポインタです。
第3番目の引数に特定されるセレクタを表す文字列は、このインレットの別のセレクタを表す文字列(第4番目の引数)で代用するためのものです。
このセレクタの代用のため、左端のインレットに関する特定のセレクタを伴うメッセージとして、特定の右側インレットに関するメッセージは扱われます。
扱われ方:
・代用するセレクタはセットアップルーチンの class_addmethod によって宣言されていなければなりません。
・このインレットのセレクタを伴うメッセージを左端のインレットへ送ることによって、特定の右側インレットをシミュレートすることができます。
・1つより多くのセレクタを目的とする、メソッドを右側インレットに加えることはできません。特に任意のセレクタの万能なメソッドを右側インレットに加えることはできません。


floatinlet_new(&x->x_obj, &x->step);

floatinlet_new は数値のための新しいパッシブなインレットを生成します。パッシブなインレットは、データ領域のメモリの一部が外部から直接書込まれることを許可します。したがって、不正な入力をチェックすることはできません。
第1番目の引数は、オブジェクトの内部基盤へのポインタです。第2番目の引数は、他のオブジェクトが書込むこともできる、データ領域のメモリのアドレスです。
パッシブなインレットは、シンボル、数値(浮動小数点*3)ポインタのために作成されます。

x->f_out = outlet_new( &x->x_obj, &s_float );

x->b_out = outlet_new( &x->x_obj, &s_bang);

outlet_new によって返されたポインタは、後にアウトレットのルーチンによって使われるために、クラスのデータ領域に保存されなくてはなりません。
オブジェクトのグラフィカル表現で、インレット、アウトレットの順序に対応するため、インレット、アウトレットの生成の順序は重要です。
---------------------
*3クラスのデータ領域のステップ幅が t_float型として実装されているからです。
---------------------


4.4 extended method space
bang メッセージのためのメソッドは、より複雑なタスクですべて埋め尽くされていなければなりません。

void counter_bang (t_counter *x)
{
t_float f = x->i_count;
t_int step = x->step;
x->i_count += step;

if (x-i_down - x->i_up) {
if ((step>0) && (x->i_count > x->i_up)) {
x->i_count = x->i_down;
outlet_bang (x->b_out);

} else if (x->i_count < x->i_down) {
x->i_count = x->i_up;
outlet_bang (x->b_out);
}
}

outlet_float (x->f_out, f);
}

それぞれのアウトレットはこのアウトレットへのポインタを通して、outlet... 関数によって識別されます。
残りのメソッドをまだ実装しなければなりません:

void counter_reset (t_counter *x)
{
x->i_count = x->i_down;

}

void counter_set (t_counter *x, t_floatarg f)
{
x->i_count = f;
}

void counter_bound (t_counter *x, t_floatarg f1, t_floatarg f2)
{

x->i_down = (f1<f2)?f1:f2
x->i_up = (f1>f2)?f1:f2
}

4.5 the code: counter
#include "m_pd.h"

static t_class *counter_class;


typedef struct _counter {
t_object x_obj;
t_int i_count;
t_float step;
t_int i_down, i_up;
t_outlet *f_out, *b_out;
} t_counter;


void counter_bang (t_counter *x)
{
t_float f = x->i_count;
t_int step = x->step;
x->i_count += step;
if (x-i_down - x->i_up) {

if ((step>0) && (x->i_count > x->i_up)) {
x->i_count = x->i_down;
outlet_bang (x->b_out);
} else if (x->i_count < x->i_down) {

x->i_count = x->i_up;
outlet_bang (x->b_out);
}
}
outlet_float (x->f_out, f);
}


void counter_reset (t_counter *x)
{
x->i_count = x->i_down;
}

void counter_set (t_counter *x, t_floatarg f)
{
x->i_count = f;

}

void counter_bound (t_counter *x, t_floatarg f1, t_floatarg f2)
{
x->i_down = (f1<f2)?f1:f2
x->i_up = (f1>f2)?f1:f2
}

void *counter_new (t_symbol *s, int argc, t_atom *argv)
{
t_counter *x = (t_counter *)pd_new(counter_class);
t_float f1 = 0, f2 = 0;

x->step = 1;
switch (argc) {
default:
case 3:

x->step = atom_getfloat (argv + 2);
case 2:
f2 = atom_getfloat (argv + 1);
case 1:
f1 = atm_getfloat (argv);
break;
case 0:
}

if (argc<2)f2=f1;

x->i_down = (f1<f2)?f1:f2;
x->i_up = (f1>f2)?f1:f2;

x->i_count = x->i_down;


inlet_new (&x->x_obj, &x->x_obj.ob_pd,
gensym("list"), gensym("bound"));
floatinlet_new (&x->x_obj, &x->step);

x->f_out = outlet_new (&x->x_obj, &s_float);

x->b_out = outlet_new (&x->x_obj, &s_bang);

return (void *)x;
}

void counter_setup(void) {
counter_class = class_new(gensym("counter"),
(t_method)counter_new,

0, sizeof(t_counter),
CLASS_DEFAULT,
A_GIMME, 0);

class_addbang(counter_class, counter_bang);
class_addmethod(counter_class,
(t_method)counter_reset, gensym("reset"), 0);
class_addmethod(counter_class,
(t_method)counter_set, gensym("set"),

A_DEFFLOAT, 0);
class_addmethod(counter_class,
(t_method)counter_bound, gensym("bound"),
A_DEFFLOAT, A_DEFFLOAT, 0);

class_sethelpsymbol (counter_class, gensym("help-counter"));
}

5 a signal-external: pan~

0 件のコメント:

コメントを投稿