Max 5 API Reference

ITM

ITM は Max 5 で導入されたテンポベースのタイミングシステム(時間処理システム)です。

これにより、ユーザが時間を表す場合にミリ秒、サンプル数、ISO 8601の "時間-分-秒" によるフォーマットだけでなく、テンポを基準とした相対的なユニットで表すことも可能になります。さらに、ITM は1つ、または複数の transport をサポートし、外部ソースと同期させることができます。ITM を認識するオブジェクトは、transport が指定した時刻に達した場合、または現在の transport のステート(状態)を調べることによってイベントの発生をスケジュールすることができます。

ITM API は2つの異なったレベルで提供されます。 Time オブジェクト (t_timeobject) インターフェイスは、時間フォーマット情報を分析し、イベントのスケジュールを行なうための高レベルな手段を提供します。さらにITM オブジェクト(t_itm)に直接アクセスするための低レベルのルーチンを使うことができます。ITM オブジェクトは現在時刻の維持管理やイベントのスケジュールといった役割を持っています。Max では複数の ITM オブジェクトを存在させることができ、各々は他と独立して動作することができます。

テンポラリイベントのスケジュール

ITM には2種類のイベントがあります。テンポラリイベントは Max の clock オブジェクトと似たものです。イベントはこの中でスケジュールされ、動的に割り当てられた時刻に発生します。これらは1度実行されると、スケジューラから取り除かれます。パーマネントイベントは、transport が指定された時刻に達すると常に発生し、スケジューラから取り除かれることはありません。ITM を認識する metro はテンポラリイベントを使用するオブジェクトの例です。これに対し、timepoint オブジェクトはパーマネントイベントを使用します。SDK の delay2 の中では、この2つのタイプのイベントがどのように動作するかを実例で示しています。Max が持つ既存の delay オブジェクトはこの機能を提供します。しかし、この例ではタイムオブジェクトインターフェイスでできることのほとんどを示しています。delay2 という実例 をよく観察し、完全なオブジェクトを見て下さい。私たちは、オブジェクトのよりシンプルなバージョンを導入し、その後、クォンタイズ処理を追加し、低レベルの ITM 呼び出しに基づいてディレイされる bang を生成するアウトレットを追加しています。

ITM time オブジェクト API は、作成したMax オブジェクトに基づいています。これは、ITM を使用する共通のやり方で実装されます。その中には、アトリビュートのサポート、クォンタイズ処理、そして、必要であれば、インターフェイスを用いて、これまでのミリ秒に基づいたタイミングとテンポに基づいたタイミングを切り替える機能が含まれます。この機能は、metro や delay のような既存のMax オブジェクトとの整合性があります(アトリビュートについてよく理解できていない場合には、この先を読み進む前に Attributes の中のアトリビュートに関する説明に目を通しておく必要があるでしょう)。

time オブジェクトを使うためには、まず、あなたのオブジェクトの中にスペースを確保し、作成するオブジェクトへのポインタを保持しておく必要があります。

    typedef struct _delay2simple
    {
        t_object m_ob;
        t_object *m_timeobj;
        void *m_outlet;
    } _delay2simple;

次に、main ルーチンの中で class_time_addattr() 関数を使い time オブジェクトに関連付けられたアトリビュートを作ります。

        class_time_addattr(c, "delaytime", "Delay Time", TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK | TIME_FLAGS_TRANSPORT);

第2の引数 "delaytime" はアトリビュートに名前をつけるための文字列です。オブジェクトのユーザは、delaytime メッセージを送信することによってディレイ値を変更することができます。"Delay Time" は、ユーザがインスペクタ内でアトリビュートを確認するためのラベルです。フラグによる引数で time オブジェクトのタイプを望むようにカスタマイズすることができます。 TIME_FLAGS_TICKSONLY は、オブジェクトがテンポに基づく(tempo-relative)ユニットによってのみ指定されることができるということを意味します。ユーザが絶対時間(ミリ秒など)を指定した場合にはオブジェクトが標準的なMax スケジューラを使用するようにしたいのであれば、このフラグは使用しません。 TIME_FLAGS_USECLOCK は、これが実際にイベントのスケジュールを行なう time オブジェクトであることを意味します。このフラグを使用しない場合、手動でイベントをスケジュールするための時間の値を time オブジェクトを使って保持、変換することができます。TIME_FLAGS_TRANSPORT は、transport の名前を指定するための追加のアトリビュートが自動的にオブジェクトに追加されるということを意味します(これは、"transport" と呼ばれ、"Transport Name" というラベルを持ちます)。上記のフラグの組み合わせは、テンポラリベースでイベントのスケジュールを行なうオブジェクトに適しています。これは、transoport 、および指定されたテンポに基づく(tempo-relative)ユニットだけに同期します。

次のステップは、インスタンス生成ルーチン(new instance routine)の中で time_new を使って time オブジェクトを作ることです。 time_new 関数は clock_new と似たようなものです。この関数に対し、スケジューラが特定の時刻に達したときに実行されるタスク(task)関数を渡します(このケースでは、delay2simple_tick がこれにあたります。この関数は bang を送信します)。 time_new への第1の引数は、あなたのオブジェクトへのポインタです。第2の引数はclass_time_addattr を介して作られるアトリビュートの名前です。第3の引数はタスク関数、第4の引数は time オブジェクトの動作をコントロールするフラグです。これについては、上記の class_time_addattr の所で説明しています。

最後に、time_setvalue を使ってディレイの初期値を 0 にセットします。

    void *delay2simple_new()
    {
        t_delay2simple *x;
        t_atom a;
 
        x = (t_delay2simple *)object_alloc(s_delay2simple_class);
        x->m_timeobj = (t_object *)time_new((t_object *)x, gensym("delaytime"), (method)delay2simple_tick, TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK);
        x->m_outlet = bangout((t_object *)x);
        atom_setfloat(&a, 0.);
        time_setvalue(x->d_timeobj, NULL, 1, &a);
        return x;
    }

ディレイされる bang を作るためには delay2simple_bang 関数が必要です。これにより time オブジェクトは自らが持つタスク関数を ITM スケジューラの中に置きます。これは time_schedule を使って行なうことができます。 覚えておいて欲しいのは、類似した clock_fdelay とは異なり、ディレイタイムが引数であるという点です。時間を表す値は、それ以前に time_setvalue を使って time オブジェクトの内部に保存されていなければなりません。time_scheduleに対する第2の引数は別の time オブジェクトです。これは、イベントのクォンタイズをコントロールするために使用することができます。delay2 のシンプルなバージョンではクォンタイズを使用しないため、ここでは NULL を渡しています。

    void delay2simple_bang(t_delay2 *x)
    {
        time_schedule(x->d_timeobj, NULL);
    }

次に示すのは、シンプルなタスクルーチン delay2simple_tick です。time オブジェクトの中で指定されたティック数が経過して time_schedule が呼び出された後にタスクルーチンが実行されます。

    void delay2_tick(t_delay2 *x)
    {
        outlet_bang(x->d_outlet);
    }

それでは、delay2 が持つ2つの高度な機能を追加してみましょう。クォンタイズ処理と低レベル ITM ルーチンを使用した第2の bang (クォンタイズされないもの)の出力です。 次に示すのは、delay2 のデータ構造体です。新しい要素は、プロキシ(ディレイタイムを受け取ります)、クォンタイズ処理で用いる time オブジェクト(d_quantize)、低レベル ITM スケジューリングで用いられる clock 、低レベル clock のタスク(task)を使用するためのアウトレットです。

    typedef struct delay2
    {
        t_object d_obj;
        void *d_outlet;
        void *d_proxy;
        long d_inletnum;
        t_object *d_timeobj;
        t_object *d_outlet2;
        t_object *d_quantize;
        void *d_clock;
 } t_delay2;

初期化ルーチンでは、クォンタイズタイムのアトリビュートを定義します。これは、作成する d_quantize time オブジェクトと共に動作します。このアトリビュートは特に独自の clock を持っているわけではありません。これは単に時間の値を保持するものです。この時間の値は ティック(tick) でのみ指定できます(ミリ秒によるクォンタイズを行なおうとしても、 ITM コンテキストでは理解されません)。 delay2 をビルドしてインスペクタを開くと、Delay Time と Quantization という2つの時間に関するアトリビュートが表示されることがわかります。

    class_time_addattr(c, "quantize", "Quantization", TIME_FLAGS_TICKSONLY);

次に示すのは、修正された delay2 のインスタンス生成ルーチン(new instance ルーチン)です。time オブジェクトを2つ、通常のclock オブジェクトを1つ生成するようになっています。

    x->d_inletnum = 0;
    x->d_proxy = proxy_new(x, 1, &x->d_inletnum);
    x->d_outlet2 = bangout(x);
    x->d_outlet = bangout(x);

    x->d_timeobj = (t_object*) time_new((t_object *)x, gensym("delaytime"), (method)delay2_tick, TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK);
    x->d_quantize = (t_object*) time_new((t_object *)x, gensym("quantize"), NULL, TIME_FLAGS_TICKSONLY);
    x->d_clock = clock_new((t_object *)x, (method)delay2_clocktick);

クォンタイズ処理を行なう time オブジェクトを使用するには、これを time_schedule に第2の引数として渡します。クォンタイズ処理の値が 0 の場合には効果を及ぼしません。しかし、time_schedule がイベントタイムを移動させ、これをクォンタイズの境界に置きます。例えば、クォンタイズ処理の値が 4n(480 ティック)、ディレイタイムが 8n(240 ティック)で、カレントタイム(現在時刻)が 650 ティックの場合、ディレイタイムが調整され、delay2 オブジェクトから出力される bang は 890 ティックではなく、980 ティックになります。

delay2_bang では、time_schedule でクォンタイズ処理を使用することに加え、itm_tickstoms を使って、ITM 時間をミリ秒時間に変換するための計算方法を示しています。このディレイの値はクォンタイズされませんが、必要であれば d_quaintise オブジェクトから時間の値を取り出して、クォンタイズされたディレイの値を独自に計算することができます。作成されている clock は delay2_clocktick を使用するため、「計算された」ディレイは右アウトレットから出力されます。

    void delay2_bang(t_delay2 *x)
    {
        double ms, tix;
 
        time_schedule(x->d_timeobj, x->d_quantize);
 
        tix = time_getticks(x->d_timeobj);
        tix += (tix / 2);
        ms = itm_tickstoms(time_getitm(x->d_timeobj), tix);
        clock_fdelay(x->d_clock, ms);
    }
 
    void delay2_clocktick(t_delay2 *x)
    {
        outlet_bang(x->d_outlet2);
    }

パーマネントイベント

ITM のパーマネントイベントは transport が特定の時刻に達したときに発生するようにスケジュールされたものです。パーマネントイベントはティック(tick)あるいは「小節/拍/ユニット(bars/beats/units)」の形式で指定することができます。ティックに基づいたイベントは transoport が特定の tick の値に達したときに発生し、「拍子記号(time signature)」が変更されても影響を受けません。「小節/拍/ユニット」で時刻を指定されたイベントは、「拍子記号(time signature)」の影響を受けます。例えば、小節/拍/ユニットが 2/1/0 で指定されたイベントを考えてみましょう。このイベントがスケジュールされる ITM オブジェクトの「拍子記号」が 3/4であったとすると、このイベントは 480 X 3、すなわち 1440 ティックで発生します。しかし、「拍子記号」が 4/4 であればイベントは 1920 ティックで発生します。逆に、このイベントが 1920 ティックで発生するようにスケジュールし、「拍子記号」を3/4 に設定しても、発生時刻には影響しません。

パーマネントイベントを「スケジュール」しない場合でも、パーマネントイベントが作成されると、常に ITM オブジェクトのパーマネントイベントのリストに置かれます。イベントが発生する時刻を指定するためには、time_setvalue を使用します。

高レベルの time オブジェクトインターフェイスはパーマネントイベントを処理します。仮に「ターゲットタイム」と呼ばれる時間の値が必要だとしましょう。最初に、class_time_addattr を使って アトリビュートを宣言します。フラグには TIME_FLAGS_TICKSONLY (パーマネントイベントの指定にはミリ秒を使用できないため、これが必要です)、 TIME_FLAGS_PERMANENT (小節/拍/ユニットによる時刻表示 1 1 0 を 0 ティックと解釈します)、TIME_FLAGS_PERMANENT (パーマネントイベント用であることを示します)、 TIME_FLAGS_TRANSPORT (これにより、ユーザが transport オブジェクトをイベントのデスティネーションとして選択することを可能にする transport アトリビュートを追加します)、 TIME_FLAGS_POSITIVE (ティック、あるいは 小節/拍/ユニットが正の値の場合にのみ発生するように、イベントに対して制限を与えます)を使用します。

    class_time_addattr(c, "targettime", "Target Time", TIME_FLAGS_TICKSONLY | TIME_FLAGS_LOCATION | TIME_FLAGS_PERMANENT | TIME_FLAGS_TRANSPORT | TIME_FLAGS_POSITIVE);

TIME_FLAGS_TRANSPORT フラグは特に役立つものです。これは、他に何の影響も与えず、オブジェクトのために transport アトリビュートを作り、ユーザが指定した transport 上にグローバルな ITM オブジェクトのデフォルト値を使ってパーマネントイベントをスケジュールしてくれます。ユーザが transport を変更したときにイベントの発生を動的に再スケジュールする必要がある場合、次のように オブジェクトを reschedule メッセージに対応させることができます。

    class_addmethod(c, (method)myobject_reschedule, "reschedule",   A_CANT, 0);         // 動的な transport の再割当て

eschedule メソッドで行なうべきことは、ユーザが時間の値を変更した場合に現在の時刻を使って time_setvalue を呼び出すという動作だけです。オブジェクト生成ルーチン( new instance ルーチン)の中で time_new によってパーマネントイベントを作りますが、その際には、class_time_addattr に渡したものと同じフラグを用います。

    x->t_time = (t_object*) time_new((t_object *)x, gensym("targettime"), (method)myobject_tick, TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK | TIME_FLAGS_PERMANENT | TIME_FLAGS_LOCATION | TIME_FLAGS_POSITIVE);

パーマネント time オブジェクトが呼び出すタスクは、clock のタスクや ITM のテンポラリイベントのタスクと同じものです。

クリーンアップ

オブジェクトの free メソッドでは、すべての time オブジェクトを解放しなければなりません。これは、パーマネントであってもテンポラリであっても同様です。これを行なわないと、オブジェクトが解放されても ITM スケジューラにイベントが残ってしまい、クラッシュを引き起こす結果になります。次に示すのは delay2 の オブジェクト消滅ルーチン(free ルーチン)の例です。

    void delay2_free(t_delay2 *x)
    {
        freeobject(x->d_timeobj);
        freeobject(x->d_quantize);
        freeobject((t_object *) x->d_proxy);
        freeobject((t_object *)x->d_clock);
    }

Copyright © 2008, Cycling '74