Javascript チュートリアル 3:
JavaScriptのタスク(Task)、アーギュメント、グローバルオブジェクト

イントロダクション

js オブジェクトでは、Max スケジューラを使う JavaScript 関数を作ることが可能です。これらの関数は、js オブジェクトへのMax メッセージによってトリガする(実行開始のきっかけを与える)ことができます。関数が呼び出される時間間隔、関数が繰り返される回数(無限に繰り返されるかどうかを含みます)、関数が直ちに実行されるか、将来のある時点で実行されるかは、あなたのコードによって決定されます。

このチュートリアルでは、JavaScript の中でどのようにスケジューリングが実行されるかを見ていきます。その中で、Max での JavaScriptの実装における、他の2つの重要な機能を見ていきます。それは、js オブジェクトのアーギュメント(これにより、オブジェクトボックスから、JavaScript コードに直接アーギュメントを渡すことができます)、およびグローバルオブジェクト(これにより、内部的な js のデータ構造体とMax の間でデータを共有することができます)です。

JavaScript におけるスケジューリング

チュートリアルパッチを開いて下さい。
チュートリアルパッチを見て下さい。4つの js オブジェクトがあることがわかると思います。これらはすべて同じ JavaScript ソースファイル( “globaltask.js”というファイル)を使っています。このソースファイルは、ディスクの中のチュートリアルパッチと同じフォルダに保存されています。
パッチ上部の ‘send a bounce’と表示された button オブジェクトをクリックして下さい。4つの js オブジェクトが数値の生成を開始し、この値がMIDI シンセサイザへのアウトプットとして(makenote と noteout オブジェクトを通じて)送り出されます。noteout オブジェクトをダブルクリックして、有効なシンセサイザ出力を選ぶと、出力されたノートの音を聞くことができるはずです。4つの js オブジェクトの左アウトレットには、もう1つの button オブジェクトが接続され、これによって、値が送り出されるようすを視覚的にフィードバックすることができるようになっています。加えて、view というパッチャーオブジェクトは、button オブジェクトからbang メッセージを受け取ると、multislider オブジェクトを使って、生成されるリズムの視覚的なフィードバックをスクロールによって描画します。
生成されるピッチが異なっているだけでなく、4つのオブジェクトの持つ固有なタイミングがそれぞれ異なっている点に注意して下さい。これは、js オブジェクトのアーギュメントによって決定されています。(詳細は後述します)
js オブジェクトで使用されているJavaScript コードは、指数関数的にタイミングを短くする関数のシンプルな例(硬い床にゴムボールを落下させた場合に類似しています)です。MIDI ノートの送信は、送信のスピードが閾値(このスクリプトの場合、5ミリ秒)を超えるまで指数関数的に増加します。閾値を超えると、関数は停止し、タイミング関数が停止したことを知らせるために、 js オブジェクトの右アウトレットからbangが送信されます。このような「実行が終わったことを知らせるbang」の使い方は、Maxオブジェクト間でタスクが完了したことを知らせるための、一般的な慣例となっています。(line、Uzi、coll、等を参照して下さい)。
'repeat' と表示された toggle をクリックし、上部の button を再びクリックして下さい。js オブジェクトからのbang によって自分自身がトリガされるため、MIDIノートの「バウンド」のサイクルが繰り返される点に注目して下さい。4つの js オブジェクト間でのタイミングの加速の違いのため、サイクルを数多く繰り返ス間に位相のズレが生じます(multislider に表示されるようすを観察して下さい)。再び toggle をクリックすると、各オブジェクトはその時点でのバウンドのサイクルが完了するまで実行され、停止します。パッチのjs オブジェクトのどれか1つをダブルクリックして、そのコードについて調べてみましょう。

スケジュールどおりの進行

スクリプトのグローバルコードを調べましょう。

// インレットとアウトレット inlets = 1; outlets = 2; // グローバル変数の定義と初期値の設定 var tsk = new Task(mytask, this); // メインのタスク var count = 0; var decay = 1.0; // アーギュメントの初期値 var dcoeff = -0.0002; // ディケイ(減衰)の係数 var note = 60; // バウンドをトリガされるMIDIノート // アーギュメントの処理(ディケイ係数、トリガされるノート) if(jsarguments.length>1) // argument 0 js ファイルの名前 { dcoeff = jsarguments[1]; } if(jsarguments.length>2) { note = jsarguments[2]; } // グローバル (Max のネームスペース) 変数 glob = new Global(“bounce”); glob.starttime = 500;

JavaScript ファイルのグローバルコードのセクションは、おなじみのインレット、アウトレットの定義(これまでに2回登場しました)、そして変数定義です。変数定義には、今までに紹介していないものが、いくつか含まれています。その第1は Taskという特別なオブジェクトを割当てられる変数についてです。

var tsk = new Task(mytask, this); // メインのタスク

これによって、JavaScript によって新しい Task オブジェクトが作られ、 tsk という名前で参照されます。tsk のためのメソッドを呼び出すと、mytask() という関数のスケジューリングと関係づけられます。Task をコントロールするオブジェクトは、私たちの js オブジェクトになります(jsオブジェクトを'this'で参照しています)。このタスクを1回実行したい時には、次のように書きます。

tsk.execute(); // タスク関数を1回実行します。

このタスクを 250 ミリ秒毎に 20 回繰り返したい場合は、次のように書きます。

tsk.interval = 250; tsk.repeat(20);

repeat() メソッドにアーギュメントが与えられない場合、次のようなキャンセル処理を行うまでTask は無限にスケジュールされます。

tsk.cancel(); // タスクをキャンセルします。

JavaScript によって繰り返されるイベントをスケジュールするために必要になる柔軟な処理は、すべて  execute(), repeat(),cancel() メソッドによって提供されます。Task オブジェクトの‘interval’プロパティに加え、例えば、タスクが実行されているかどうか(running)、すでに何回呼び出されているか(iterations)について検出することもできます。

js オブジェクトの中の全てのメソッドが(Max メッセージによってトリガされるか、タスクによって内部的にスケジュールされるかに関わらず)、Max スケジューラの低い優先度で実行されるということは、覚えておかなければならない重要な点の1つです。このことから、メソッドは常にMax の中で正しい順序で実行やデータ送信を行いますが、スケジューラが他のアクションによって負荷をかけられている場合、極めて正確なタイミングで実行されるかどうかはあてにできません。

Taskである tsk の定義が完了した後、js オブジェクトへの bang() メソッドによってこれをトリガします。

function bang() { tsk.cancel(); // バウンドが実行されている場合には、それをキャンセル。 count = 0; // バウンドの数をリセット。 decay = 1.0; // decay の初期値をリセット。 tsk.interval = glob.starttime; // タスクのインターバルの初期値をセット。 tsk.repeat(); // バウンドのスタート。 }

どのjs オブジェクトも 、bang を受け取ると、すでにスケジュールされている tsk タスクをすべてキャンセルし、タスク関数に関係した変数をリセットし、タスクのインターバルの初期値をセットして、再びタスクをスタートさせます。

チュートリアルパッチの上部にある button をクリックして、「バウンド」をスタートさせて下さい。stop と書かれたメッセージボックスをクリックして下さい。MIDI ノートの送信は終了します。この stop() 関数は、tsk のcancel() メソッドの呼出しによって、すでにスケージュルされたタスクをキャンセルするだけのものです。

function stop() { tsk.cancel(); // タスクのキャンセル。 }

目前のタスク(The Task at Hand)

Task オブジェクトは、bang() メソッドによって動作を開始させられると、最初の宣言で定義された関数を呼び出します。

mytask() 関数のコードを詳しく調べてみましょう。

// mytask -- スケジュールされたタスク - // 数値を出力し、次のタスクをスケジュールします。 function mytask() { if(arguments.callee.task.interval>5) // バウンドを続けます。 { outlet(0, note); // MIDI ノートの値を送信。 decay = decay*Math.exp(++count*dcoeff);// ディケイ変数の // インクリメント。 arguments.callee.task.interval // タスクのインターバルの更新。 =arguments.callee.task.interval*decay; } else // バウンドのインターバルが小さすぎるので, 「床に止まった」と考えます。 { arguments.callee.task.cancel(); // タスクのキャンセル。 outlet(1, bang); // バウンドが終了したことを示すために、右アウトレットから // bang を送信。 } }

今までJavaScript チュートリアルで使ってきた他の関数と異なり、このmytask() 関数は js オブジェクトの外からのMax メッセージによってトリガされることを意図していません。デフォルトでは、js オブジェクトで宣言された関数はすべて、Max環境から送られる、それに見合った名前のメッセージに対して応答します。しかし、この mytask() は パッチャーからの mytask メッセージによってトリガされたくないため、関数の処理を書き終わった後、次の1行のコードを置きます。

mytask.local = 1;

この文によって、mytask() は、js 環境内のローカルなメソッドとなり、外部からのアクセスができなくなります。

タスク関数は2つのことを行います。この関数は、Max に整数値を送り(MIDI ノートをトリガします)、同時に自分自身のタイミングのインターバルをインクリメントさせることによって次の mytask() の実行がやや早く行われるようにします。タスク関数の外部から、タスクのinterval プロパティをセットする(例えば、tsk.interval = 250など)ことによってタイミングのインターバルを変更できます。Task オブジェクトのプロパティとメソッドは、タスク関数の中でTask をcallee として参照することによって変更できます。これは次のように行います。

arguments.callee.task.interval=250; // タスクのタイミングを250 // 合わせます。 arguments.callee.task.cancel(); // 自分自身でタスクをキャンセルします。

ここでは、Task オブジェクトのタイミングのインターバルをタスク関数内から変更するために、この再帰の機能を使っています。タイミングのインターバルが十分小さい値(この例では 5 ミリ秒)まで減少すると、再びこの機能を使って、タスク関数に、最初に呼び出されたTask をキャンセルさせます。

Math オブジェクトの使用

再びmytask() のコードを見てみましょう。バウンドごとにディケイ(decay) の値を変更している行に注意して下さい。

decay = decay*Math.exp(++count*dcoeff); // ディケイ(decay) 変数の // インクリメント。

JavaScript には、JavaScript と Max の間で相互作用を行うことができるオブジェクト(Maxobj,Task)に加え、js のためのプログラムを書く際に役立つ多くのコアオブジェクトがあります。Math オブジェクトは組み込みのプロパティとメソッドによる大きなライブラリを持ち、一般的に必要となる数学関数の実行を可能にします。私たちのコードでは、Math オブジェクトの exp() メソッドを使っていますが、このメソッドは eの値(自然対数の基底で、およそ 2.71828 になります)の指数として引数(この例では、decay の係数として次の ボールのカウントに掛けられています)を使い、その値を返します。この方法はバウンドイベントの指数関数的な増加のモデリングに重要な役目をはたしています。
JavaScript のMath オブジェクトはおおよそ C の math(数学演算) ライブラリ、あるいはMax の expr オブジェクト(これ自身、C の math ライブラリに基づいています)と類似した機能を持っています。他の多くの定義済みのコアオブジェクト(例えば、Date, String)は、概ね、該当するC のライブラリ(例えば time,string)に見合った言語の拡張を提供します。

js オブジェクトへのアーギュメント

このスクリプトの2つの変数(dcoeff と note)は、js オブジェクトに渡されるアーギュメントによって決定されています。これらのアーギュメントは、グローバルコードブロックで解析されています。これは、js オブジェクトの jsarguments プロパティをチェックすることによって行われます。

if(jsarguments.length>1) // アーギュメント 0 js ファイルの名前 { dcoeff = jsarguments[1]; } if(jsarguments.length>2) { note = jsarguments[2]; }

アーギュメント 0 が JavaScript ファイルの名前(この例では、”glovaltask.js”)になる点にに注意して下さい。このため、実際には、ほとんどの場合アーギュメント 1 から見ていくことになります。上のコードでは、変数にアーギュメントを割当てようとする前に、アーギュメントが存在しているかどうかを確認するためにチェックを行っています。

グローバルオブジェクト

js オブジェクトのエディタを閉じて、しばらくの間チュートリアルパッチに戻りましょう。左下隅の、;bounce starttime $1 と書かれたメッセージボックスに接続されているナンバーボックスを見て下さい。ナンバーボックスに 2000 とタイプして値を確定して下さい。最上部の button をクリックしてバウンドするデータを送信して下さい。すべてのオブジェクトのバウンスとバウンスの間のタイミングが以前より広くなったことに注意して下さい。ナンバーボックスの値を小さくしてみて下さい。タイミングの間隔がより短くなるはずです。

このメッセージボックスが、この Max パッチの中のどこかに存在する bounce という receive オブジェクトにstarttime 2000 というメッセージを送ったのではないかということが予想できます。実際、このメッセージは、私たちの js オブジェクトの中にあるグローバルオブジェクト(bounce という名前に応答するように指定されています)の starttime プロパティを 2000 にセットします。これは、グローバルコードの中で、次のようにグローバルオブジェクトを宣言することによって行われています。

// グローバル (Maxネームスペース) 変数 glob = new Global(“bounce”); glob.starttime = 500;

コードの中では、変数(glob) を作り、それに 新しい Global オブジェクトを割当てています。グローバルオブジェクトのアーギュメント(“bounce”)は、Max ネームスペースの中の名前で、オブジェクトに結びつけられています。Max の中で bounce に送られるどのメッセージも、この名前を使っている Global オブジェクトのプロパティをセットしようとします。内部的には、Max と js オブジェクトが通信を行なうシンボルによってではなく、私たちが選んだ変数名(glob)によってGlobal オブジェクトを参照している点に注意して下さい。

ここでは、グローバルブロックの中でプロパティ(“starttime”)を単に指定するだけで、このプロパティを私たちのオブジェクトに追加しています。これにより、starttime で始まるメッセージは私たちの Max パッチの中のbounceに送られ、そのアーギュメントによってこのプロパティがセットされます。

さらに、このオブジェクトは、Max が js オブジェクトの外部から値のセットを行うことが可能であるというだけではなく、複数の js オブジェクトがこのオブジェクトの特定のインスタンスとそのプロパティをを共有しているという意味で、実際にグローバルなものであると言えます。この機能は、Max が複数の js オブジェクトに対してブロードキャストを行うことを可能にすると同時に、複数の js オブジェクトに情報を共有させるために使うこともできます。

結び

JavaScript では、Task オブジェクトを使って動的にイベントをスケジュールすることができます。Task を生成し、それを Task によって呼び出される関数に結びつけることによって、タスクの起動やキャンセル、タスクのタイミングのインターバルやそれが何回繰り返されるかの設定を行うことができます。さらに、Task によって呼び出される関数の “callee” プロパティを使うことによって、スケジュールされたイベント自身の中でこれらを設定することができます。js オブジェクトの全てのメソッド(内部的に呼び出されるものでも、Max メッセージによって呼び出されるものでも)はスケジューラの低い優先度で実行されます。

JavaScript には多くのコアオブジェクトがあり、このコアオブジェクトは、必要とされる可能性がある共通のプログラミングルーチンの機能を提供します。例えば、Math オブジェクトは、C の math ライブラリや Max のexpr オブジェクトで見られるような様々な数学関数にアクセスすることを可能にしてくれます。

js オブジェクトへのアーギュメントは、オブジェクトの “jsarguments”プロパティによって取り扱われます。オブジェクトは、そのアーギュメントのナンバー付けを 0 から始めますが、js の最初のアーギュメントはロードするソースファイルの名前になっています。

JavaScript の Global オブジェクトは js オブジェクト間のコミュニケーションを可能にし、Max 環境から直接オブジェクトのプロパティのセットを行うことを可能にします。

コードリスト

// globaltask.js // // 指数関数的に減少する時間のカーブによって指定された時刻で、数値のストリームを // 生成します。アーギュメントによってカーブをセットし、値を出力します。 // rld, 5.04 // // インレットとアウトレット inlets = 1; outlets = 2; // グローバル変数の定義と初期値の設定 var tsk = new Task(mytask, this); // メインのタスク var count = 0; var decay = 1.0; // アーギュメントの初期値 var dcoeff = -0.0002; // ディケイ(減衰)の係数 var note = 60; // バウンドをトリガされる MIDI ノート // アーギュメントの処理(ディケイ係数、トリガされるノート) if(jsarguments.length>1) // argument 0 js ファイルの名前 { dcoeff = jsarguments[1]; } if(jsarguments.length>2) { note = jsarguments[2]; } // グローバル (Maxのネームスペース) 変数 glob = new Global(“bounce”); glob.starttime = 500; // bang -- タスクのスタート function bang() { tsk.cancel(); // バウンドが実行されている場合には、それをキャンセル。 count = 0; // バウンドの数をリセット。 decay = 1.0; // decay の初期値をリセット。 tsk.interval = glob.starttime; // タスクのインターバルの初期値をセット。 tsk.repeat(); // バウンドのスタート。 } // stop -- ユーザがバウンドを止めることができるようにします。 function stop() { tsk.cancel(); // タスクのキャンセル。 } // mytask -- スケジュールされたタスク。 // 数値を出力し、次のタスクを再スケジュールします。 function mytask() { if(arguments.callee.task.interval>5) // バウンドを続けます。 { outlet(0, note); // MIDI ノートの値を送信。 decay = decay*Math.exp(++count*dcoeff); // ディケイ変数の // インクリメント。 arguments.callee.task.interval // タスクのインターバルの更新。 =arguments.callee.task.interval*decay; } else // バウンドのインターバルが小さすぎるので, 「床に止まった」と考えます。 { arguments.callee.task.cancel(); // タスクのキャンセル。 outlet(1, bang); // バウンドが終了したことを示すために、右アウトレットから // bang を送信。 } } mytask.local = 1; // Max から直接タスクがトリガされるのを防ぎます。


参照

js Max の JavaScript オブジェクト