Max 5 API Reference

インレットとアウトレット

パッチ内で2つのオブジェクトを接続する場合のインレットとアウトレットについてはよく知っていると思います。

オブジェクトでデータを受信したり、他のオブジェクトへデータを送信するためには、C バージョンのインレットとアウトレットを作る必要があります。このセクションでは、インレットとアウトレットがどのようなものか、そして、これらをどのように作成し、どのように使用するかについて説明します。さらに、オブジェクトのどのインレットに対するメッセージでも受信することが可能な、プロキシと呼ばれる高機能なタイプのインレットについても述べます。プロキシは、オーディオオブジェクトで、インレットがシグナルと通常のMax メッセージの両方を取り扱うことを可能にするために使用されます。

デフォルトでは、すべてのオブジェクトはインレットを1つ持ちます。追加のインレットはデフォルトインレットの右側に表示され、右端のインレットが最後に作られます。

インレットは、基本的にメッセージの翻訳を行なうものです。例えば、int インレットを作成した場合、この新しく作成されたインレットに数値が届くと、オブジェクトは "int" メッセージの変わりに "in1" メッセージを受信します。異なったメッセージ名を使用することによって、個々のインレットに届いた数値によって特定の動作を定義することができます。例えば、+のような、Max の基本的な数値演算オブジェクトでは、右インレットに数値が届いたときに加算する数値を格納しますが、実際に演算を実行して結果を出力するのは、左インレットに数値が到着したときです。

アウトレットは、オブジェクト間の接続を定義します。そして、オブジェクトから、接続された他のオブジェクトに対してメッセージを送信するために使用されます。しかし、アウトレットの分かりにくい点は、アウトレットから数値を出力した場合、そのアウトレットから続いている演算がすべて完了するまで、アウトレットから出力を行なう関数が結果を返さないということです。このスタックに基づいた実行モデルは、Max デバッガウィンドウでパッチを観察するとよく分かります。このスタックに基づいたモデルを理解するためには、Max のデバッグ機能とブレークポイントを使い、パッチの実行をステップスルーしながら、スタック表示を追跡するとよいでしょう。アウトレットは、インレットと同様に作成順に表示され、右から左という順序になります。言い換えると、作成した第1インレットやアウトレットは見た目には右端から最も遠いものになります。

訳注:この部分の説明はわかりにくいと思いますが、次のインレットやアウトレットを作成しているコードを見たほうが理解しやすいでしょう。下のコードでは、最初に右側からインレット、アウトレットを作成するように行が並んでいます。例えば、インレットは intin(x,2) の次に intin(x, 1)が作成され(実際にはもう1つデフォルトのインレットがあります)、アウトレットは、x->m_outlet2 = bangout((t_object *)x);、 x->m_outlet1 = intout((t_object *)x); の順になっています。これで、インレットアウトレットは右側から順に作成され、最も左端が「第1」になります。

インレットの作成と使用

インレットを正しく使用するためには、2つのステップがあります。まず、初期化ルーチンでによってインレットに送られるメッセージに対応するメソッドを追加します。次に、インスタンス生成ルーチン(new instance ルーチン)でインレットを作成します(他のタイミングでのインレットの作成はサポートされていません)。

インレットには int、float、カスタムという3つのタイプがあります。ここでは int と float というタイプのインレットについてのみ説明します。これは、一般的に、任意のメッセージを受信できるようなインレットを作成する場合にはプロキシを使用するほうが良いためです。int インレットでは、関数を割り当てるインレットのナンバに従って "in1"、"in2"、"in3"、などという形でバインドします。ここでは "in1" を使って1つのインレットを作る方法を示します。

初期化ルーチンの中では、次のように書きます。

        class_addmethod(c, (method)myobject_in1, "in1", A_LONG, 0);

インスタンス生成ルーチン(new instance ルーチン)では、 object_alloc() を呼び出してインスタンスを作った後に、次のように書きます。

        intin(x, 1);

右インレットで int を受信した際に呼び出されるメソッドは次のように書きます。:

    void myobject_in1(t_myobject *x, long n)
    {
        // do something with n
    }

この方法で1つのインレットを作成した場合、オブジェクトは2つのインレットを持つことになります(デフォルトで、常に1つのインレットが作成されることを思い出して下さい)。複数のインレットを作成したい場合には、下に示すように右から左の順で作成する必要があります。

        intin(x, 2);        // creates an inlet (the right inlet) that will send your object the "in2" message
        intin(x, 1);        // creates an inlet (the middle inlet) that will send your object the "in1" message

オブジェクトに float メッセージを受け取るインレットは floatin() によって作成され、float メッセージは "ft1"、”ft2"、"ft3" などに変換されます。次はこの例です。

初期化ルーチンの中では、次のように書きます。

        class_addmethod(c, (method)myobject_ft1, "ft1", A_FLOAT, 0);

インスタンス生成ルーチン(new instance ルーチン)では、次のように書きます。

        floatin(x, 1);

メソッドは次のように書きます。

    void myobject_ft1(t_myobject *x, double f)
    {
        post("float %.2f received in right inlet,f);
    }

1つのオブジェクトに int インレットと float インレットを作成することはできますが、個々のインレットは独自のナンバを持たせなければなりません。次はその例です。

        intin(x, 2);
        floatin(x, 1);

アウトレットの作成と使用

アウトレットはインスタンス生成ルーチン(new instance ルーチン)で作成します。アウトレット作成関数はポインタを返しますが、このポインタを保存しておき、メッセージを送信する際に使用します。インレットと同様、アウトレットも右から左の順で作成されます。

次の例はシンプルなものです。まず、アウトレットをそれぞれ個別のインスタンスとして保存しておくために、オブジェクトのデータ構造体に2つの void 型ポインタを追加します。

    typedef struct _myobject
    {
        t_object m_ob;
        void *m_outlet1;
        void *m_outlet2;
    } t_myobject;

その後、インスタンス生成ルーチン(new instance ルーチン)でアウトレットを作成します。

        x = (t_myobject *)object_alloc(s_myobject_class);
        x->m_outlet2 = bangout((t_object *)x);
        x->m_outlet1 = intout((t_object *)x);
        return x;

これらのアウトレットは型を指定されているもので、常に同じ型のメッセージを送信するということを意味します。任意のメッセージを送信できるアウトレットを作成したい場合には、outlet_new() を使います。 型を指定されたアウトレットは、メッセージ送信の際に呼び出されるメソッドハンドラと直接接続されているため、より速く処理を行なうことができます。このようなアウトレットからメッセージを送信しようとする場合、例えば bang メソッドでは次のように行ないます。

    void myobject_bang(t_myobject *x)
    {
        outlet_bang(x->m_outlet2);
        outlet_int(x->m_outlet1, 74);
    }

上記の bang メソッドは、まず bang メッセージを m_outlet2 から送信し、その後、数値 74 をm_outlet1 から送信します。これは、右から左の順でアウトレットから値を送信するという Max オブジェクトの一般的な設計にうまく合致します。しかし、この設計方法には強制力があるわけではないため、必要があればこの文の順序を逆にすることも可能です。

より一般的なメッセージ送信ルーチンとして outlet_anything() があります。これに関しては Atom とメッセージ の章で説明します。

プロキシの作成と使用

プロキシ(proxy)はインレットをコントロールする小さなオブジェクトですが、インレットが受信したメッセージを解釈するわけではありません。その代わり、プロキシは、オブジェクトデータ構造体の中に用意された場所に、それぞれのインレットに対応する値(訳注:左インレットを 0 とするインレットナンバ)をセットします。メッセージが、直接、オブジェクトの左インレットに届いた場合、この値は 0 になります。 しかし、コードをスレッドセーフに(複数のスレッドから呼び出しても問題がないように)するためには、この「インレットナンバ」の値を直接読み込んではいけません。その代わり、 proxy_getinlet() ルーチンを使ってメッセージがどのインレットで受信されたかを判断します。

プロキシが通常のインレットに比べて優れた点は、オブジェクトが、左インレットだけではなく、すべてのインレットで任意のメッセージに対応することができるという点です。Max ユーザであれば、特に知識としては知らなくても、プロキシ機能を理解しているかも知れません。例えば、pack オブジェクトは、任意のインレットで受け取った int、float、リスト、シンボルを結合することができます。これは、プロキシ機能を使うことによって可能になっています。複数のインレットでシグナルを受信できるMSP オーディオオブジェクトは、同様にプロキシを使っています。実際に、プロキシ機能はオーディオオブジェクトを作成する方法に組み込まれています。これについては、Anatomy of a MSP Object セクションで述べたいと思います。

作成するオブジェクトの左インレット以外のインレットが、int あるいは float に対応するだけで良いのであれば、プロキシを利用する意味はありません。

まず、オブジェクト構造体にプロキシの値を保存する場所を追加します。これに直接アクセスすることはできませんが、プロキシにはこのスペースが必要です。次に、プロキシ自体を保存しておく必要があります。これは、このオブジェクトの消滅の際にプロキシを解放する必要があるためです。数多くのプロキシを作った場合、すべてのプロキシに体するポインタを保存しておく必要がありますが、これらすべてのプロキシは long int の値を保存する同じフィールドを共有します。

    typedef struct _myobject
    {
        t_object m_obj;
        long m_in;          // space for the inlet number used by all the proxies
        void *m_proxy;
    } t_myobject;

インスタンス生成ルーチン(new instance ルーチン)では、プロキシを作成して、オブジェクト自身へのポインタ、プロキシと関連づけられたゼロ以外のコード値、 およびオブジェクトが持つインレットナンバ位置へのポインタを渡します。

        x->m_proxy = proxy_new((t_object *)x, 1, &x->m_in);

オブジェクトに通常のインレットを作りたいのであれば、それも可能です。プロキシと通常のインレットは混在させることができますが、このような設計はオブジェクトのユーザを混乱させる可能性があります。

最後に、次のメソッドは、x->m_in の値によって異なった動作を行なうものですが、この値をチェックするために proxy_getinlet()を使っています。

     void myobject_bang(t_myobject *x)
    {
        switch (proxy_getinlet((t_object *)x)) {
            case 0:
                post("bang received in left inlet");
                break;
            case 1:
                post("bang received in right inlet");
                break;
        }
    }

Copyright © 2008, Cycling '74