チュートリアル50:
JavaScriptにおけるタスク(Task)、 アーギュメント、およびグローバルオブジェクト

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

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

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

前のJavaScript のチュートリアルと同様、チュートリアルパッチで生成される MIDI データを聴くために、MIDI シンセサイザ装置の接続方法について復習したいと思うかもしれません。チュートリアル 12 : MIDI ノートの送受信では、接続の設定とテストの方法について説明しています。

・チュートリアルパッチ 50.JavascriptGlobals.pat を開いて下さい。4つの js オブジェクトがあるのがわかると思います。これらはすべて同じ JavaScript ソースファイル( “globaltask.js”というもの)を使っていますが、このソースファイルは、ディスクの中のチュートリアルパッチと同じフォルダに保存されています。

・パッチ上部の ‘send a bounce’と表示された button オブジェクトをクリックして下さい。4つの js オブジェクトが、MIDI シンセサイザのアウトプットへ(makenotenoteout オブジェクトを通じて)送り出される値の生成を始めます。4つの js オブジェクトの左アウトレットには、追加の button オブジェクトが接続され、これによって、値が送り出される様子の視覚的なフィードバックができるようになっています。加えて、view というパッチャーオブジェクトは、button オブジェクトからbang メッセージを受け取ると、multislider オブジェクトを使って、生成されるリズムの視覚的なフィードバックのスクロールを描きます。

4つのオブジェクトの持つ固有なタイミングが、生成される画像と同様、それぞれ異なっている点に注意して下さい。これは、js オブジェクトのアーギュメントによって決定されています。(詳細は後述します)

js オブジェクトで使用されているJavaScript コードは、指数関数的にタイミングを短くする関数のシンプルな例(硬い床にゴムボールを落下させた場合に類似しています)です。MIDI ノートの送信は、送信のスピードが閾値(このスクリプトの場合、5ミリ秒)を超えるまで指数関数的に増加します。閾値を超えると、関数は停止し、タイミング関数が停止したことを知らせるために、js オブジェクトの右アウトレットからbang が送信されます。このような「実行が終わったことを知らせる bang 」の使い方は、Maxオブジェクト間でタスクが完了したことを知らせるための、一般的な慣例となっています。(lineUzicoll、等を参照して下さい)。

・'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 オブジェクトになります(これを ‘this’で参照しています)。このタスクを1回実行したい時には、次のように書きます。

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

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

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

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

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

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

覚えておかなければならない重要な事の1つは、js オブジェクトの中の全てのメソッドは(Max メッセージによってトリガされるか、タスクによって内部的にスケジュールされるかに関わらず)、Max スケジューラの低い優先度で実行されることです。これにより、メソッドは常に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 と Max(Maxobj) の間のインタラクション(相互作用)を行うことができるオブジェクトに加え、JavaScript には、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 オブジェクトのプロパティをセットしようとします。内部的には、私たちが選んだ変数名(glob)によってGlobal オブジェクトを参照しているのであって、Max と js オブジェクトがコミュニケーションを取るようなシンボルによって行っているのではないという点に注意して下さい。

グローバルブロックの中で、プロパティ(“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;


参照

js Max の JavaScript オブジェクト
expr 数式を評価する
send パッチコードなしでメッセージを送る
var 格納した数値を、他のオブジェクトと共有する