Javascript チュートリアル 2:
JavaScript によるスクリプティング

イントロダクション

Max の js オブジェクトを使うと、JavaScript コードを使ったパッチのスクリプティングが可能になります。これにより、パッチャー内でMax オブジェクトを動的に生成し、それに対してプロパティを設定し、メッセージを送り、オブジェクト間の接続を行うことができます。JavaScript では手続き的なコードを使ってパッチャーのエレメントを生成することができます。これは、thispatcher オブジェクトにメッセージを送る方法(パッチ内でオブジェクトを自動的に生成する別の方法です)によって行うのはより困難と思われる場合でも可能です。このチュートリアルでは、JavaScript によって書かれたカスタムメソッドによってMax パッチの中でのオブジェクトの生成、消滅や、オブジェクト間の接続を行う方法について説明します。同時に、パッチャーから送られてくるカスタムメッセージを扱うためのメソッドの使い方を示します。

JavaScript によるパッチスクリプト

チュートリアルを開いて下さい。

最初に チュートリアルパッチを開くと、大きな、何もないパッチャーの空間が広がり、パッチャーウィンドウの下の方に js オブジェクトがあることがわかると思います。js オブジェクトは、autosurface.js’というJavaScript のソースファイルを読み込んでいます。このソースファイルはチュートリアルパッチと同じフォルダに置かれています。

js オブジェクトは(makenote および noteout オブジェクトを使って)MIDI 出力デバイスに数値を送るように設定されています。また、右アウトレットを持ち、ここから pack オブジェクトの右インレットへ、multislider オブジェクトを動かすためのメッセージを送っています。さらに、この js オブジェクトはそのインレットへ接続される多くのオブジェクトを持っています。metro オブジェクトが js オブジェクトに接続され、sliders $1 および reverse $1 というメッセージを送信する2つのメッセージボックスが接続されています。この $1はそれぞれ接続されているナンバーボックスによって与えられる値になります。(訳注:sliders $1 と size $1 の誤りではないかと思われます)

パッチのレイアウトから、このjs オブジェクトのJavaScript コードには、少なくともbang、sliders、reverse という3つの関数がなければならないと推測することができます。実際にはもう1つの関数を持っていますが、それは、このパッチを使うことによって明らかになるでしょう。

パッチの自動生成

sliders $1 というメッセージを持ったメッセージボックスに接続されているナンバーボックスを選択して下さい。数値を入力するか、ナンバーボックスをスクロールして、ナンバーボックスの値を 5 にして下さい。そして、何が起きるかを見て下さい。ナンバーボックスの数値を変更して下さい。大きな数(50 位)に設定してみて下さい。

値を 0 に設定して、何が起きるかを見て下さい。

js オブジェクトは、sliders $1 メッセージに応答し、スクリプトによって動的にMax オブジェクトを生成、接続します。ここでは、js オブジェクトへのメッセージを通じて要求した sliders の数と同じだけ、ctlin と uslider オブジェクトのペアを作ります。さらに、uslider オブジェクトの数に見合うだけのインレットを持つfunnel オブジェクト作り、それらの間を接続します。そして、funnel オブジェクトは js オブジェクトに接続されます。これにより、スライダによって生成された値をJavaScript コードで扱うことができるようになります。

スライダを生成する際に、ctlin オブジェクトに自動的にナンバーが割り当てられ、それぞれのMIDI コントローラナンバーの情報を待ち受けるようになっている注意して下さい。結果として、MIDI コントロール装置が複数のコントローラーナンバーにMIDIコンティニュアス・コントロール値を送っている場合、その値はそれぞれ独立したuslider オブジェクトに対して送信されます。また、スライダの数を減らした場合、余分なオブジェクトが消える(実際に、すべて消滅し、再び作られています)点にも注意して下さい。sliders の値を 0 にセットすると、スクリプトによって作られたオブジェクト(funnel も含みます)はすべてパッチから削除されます。

スライダの数を 5 程度の控えめな数に設定して下さい。uslider オブジェクトの中をクリックするか、MIDI コントローラ入力を使って、usliderの 値を変更して下さい。metro オブジェクトに接続された toggleをクリックして、metro をオンにして下さい。uslider オブジェクトの値が順に js オブジェクトから出力され、MIDI ノートのシーケンスを作ります。noteout オブジェクトをダブルクリックして有効なMIDIシンセサイザを選ぶと、このシーケンスを聴くことができるはずです。

パッチの右側にある multislider オブジェクトでは、シーケンスに対応する位置に値がセットされ、シーケンサからのノート出力動作がリアルタイムで表示されています。

reverse $1 というメッセージが書かれたメッセージボックスに接続されているtoggleをクリックして下さい。slider の値がシーケンスされる順序が逆になる点に注目して下さい。同様に、multislider の表示の方向も逆になります。

簡単に言うと、この js オブジェクトは拡張可能な MIDI コントロール操作装置を(ctlin オブジェクトと slider オブジェクトで)動的に生成し、それらのオブジェクトの値によるシンプルなMIDI ステップシーケンサを作るものです。JavaScript プログラムによって作られるスライダの数が、シーケンスの長さを決定します。

2つの toggle オブジェクトをオフにしてシーケンスを停止させ、シーケンサによる転送を「foward(前進)」モードに戻して下さい。それでは、この js オブジェクトのプログラムコードを見ていきましょう。

グローバルブロック: Arrays と Maxobj

チュートリアルパッチの js オブジェクトをダブルクリックして下さい。’autosurface.js”というコードが表示されます。コードの最初は、おなじみのコメントブロックで、このスクリプトが何をするものかを説明しています。その下に、グローバルコードのステートメント(文)があることがわかります。

// インレットとアウトレット inlets = 1; outlets = 2; // グローバル変数と配列 var numsliders = 0; var seqcounter = 0; var thereverse = 0; var thevalues = new Array(128); // スクリプトのための Maxobj 変数 var controlin = new Array(128); var thesliders = new Array(128); var thefunnel;

この前のチュートリアルで見たように、コードの最初にある inlets と outlets は、js に対して オブジェクトにインレットとアウトレットがいくつ必要かを知らせるものです。次のプロックでは、JavaScript コードがグローバルとして使う必要がある変数がいくつか定義されています。変数には次のものが含まれます。

numsliders パッチの中に「スライダ」(ctlin と uslider のペア)がいくつあるかを格納します。これ は、js オブジェクトへの sliders メッセージによって設定されます。
seqcounter シーケンスの現在位置を格納します。これは、パッチ内のmetro オブジェクトによって駆動されるため、コードではbang メソッドによって変更されます。
thereverse シーケンサが逆方向に実行されるかどうかをセットします。これは、reverse メッセージによって、js オブジェクトにセットされます。
thevalues パッチの中にあるslider オブジェクトの状態を反映した値の配列(下記参照)。パッチのfunnel オブジェクトから送られるリストによって、この値がセットされます。

JavaScript では、new Array() コンストラクタによって配列を生成します。上記の配列変数thevalues は128 個の要素を持っていますが、これらにアクセスするためには、配列名の後にブラケットを付ける表記法を使います。例えば、

k = thevalues[5];

では、変数 k に配列 thevaluesの6番目(配列の添字は 0 から始まります)の値が代入されます。

thevalues[n] = 55;

では、配列 thevalues の n 番目の要素に55が代入されます。

JavaScript は 配列をオブジェクトとして扱う点に注意して下さい。そのため、

k = thevalues.length;

では、変数 k に配列 thevalues の要素の数が代入されます。詳しくは、JavaScriptのリファレンスなどを参照して下さい。

変数宣言を行なうと、Max パッチで動的に生成されるオブジェクトの参照に用いる変数が利用できるようになります。これらの変数名は、オブジェクトの生成、接続、削除、そして、プロパティを通しての変更を行うことができるように、私たちのJavaScript コードで内部的に使われるものです。js の中で、パッチャー内の Max オブジェクトを参照するために用いるオブジェクトは Maxobj と呼ばれるものです。このスクリプトの中では、次のような Maxobj 変数を用いています。

controlin パッチ内の ctlin オブジェクトを参照するMaxobj の配列
thesliders パッチ内の slider オブジェクトを参照するMaxobj の配列
thefunnel パッチ内の funnel オブジェクトを参照するMaxobj

JavaScriptの変数宣言では、変数が格納する値の型による違いがないことに注意して下さい。整数(int)、浮動小数点数(float)、文字列、オブジェクトは、変数を宣言する時点ではすべて同じものと見なされます。同様に、配列も、どのような型の情報が格納されるかについてではなく、単に情報の量(要素の数)について定義されるだけです。JavaScript は、計算の後で変数の型を正確に決定します。例えば、

x = 4/2;

では、変数 x には2(整数)が代入されるのに対し、

x = 3/2;

では、1.5(浮動小数点数)が代入されます。変数は、その寿命の中で動的に型を変更することができます。しかし、このような型指定のない変数の使用は、JavaScript 環境の中でしか行うことができないため、Max から送られる異なった型の数値を扱うためにそれぞれ独立したメソッド(msg_int()、msg_float())が必要になります。

私たちは Maxobj オブジェクトクラスの様々なプロパティを使ってスクリプトを実行しますが、それらはすべて、(サンプルプログラムの sliders() メソッドのように)1つの関数として行われます。

アーギュメント、アグリーメント..,( Arguments, Agreements…)

サンプルプログラムの js オブジェクトは、sliders メッセージに対し、sliders() 関数が持っているメソッドを使って応答します。(通常、関数名はその関数のトリガとなるメッセージと合致していることを思い出して下さい)sliders() 関数のコードを調べてみましょう。それぞれのセクションの始めにあるコメントは、処理のそれぞれのステップでどのようなことが起こるかを説明しています。

// sliders -- Max パッチの中でスライダを生成し、接続します。 function sliders(val) { if(arguments.length) // アーギュメントがない場合、処理を行いません。 { // アーギュメントの構文分析 a = arguments[0]; // スライダ数のセーフティチェック if(a<0) a = 0; // 0 の場合:スライダが少なすぎる if(a>128) a = 128; // 128 の場合:スライダが多すぎる // 古いものを取り外します。 // 一度処理を行っている場合、funnel を取り除きます。 if(numsliders) this.patcher.remove(thefunnel); // 以前の sliders の値を使って ctlin および slider オブジェクトを // 取り除きます。 for(i=0;i<numsliders;i++) { this.patcher.remove(controlin[i]); this.patcher.remove(thesliders[i]); } // 新しいものを作ります。 numsliders = a; // グローバル変数として格納する sliders の数を新しい値に // 更新します。 if(numsliders) // funnelを作ります。 thefunnel = this.patcher.newdefault(300, 300, "funnel", a); // ctlin slider オブジェクトを作り、それらをお互いに接続し、 // 同時にfunnnel と接続します。 for(k=0;k<a;k++) { controlin[k] = this.patcher.newdefault(300+(k*50), 50,“ctlin”, k+1); thesliders[k] = this.patcher.newdefault(300+(k*50), 100,“uslider”); this.patcher.connect(controlin[k], 0, thesliders[k], 0); this.patcher.connect(thesliders[k], 0, thefunnel, k); } // 新しいオブジェクトをこの js オブジェクトのインレットに接続します。 ourself = this.box; // この js オブジェクトに Maxobj を割当てます。 if (numsliders) // funnel js に接続します。 this.patcher.connect(thefunnel, 0, ourself,0); } else // アーギュメントについて説明します。 { post(“sliders message needs arguments”); post(); }
}

疑似コードを使って説明すると、sliders() 関数の処理は次のようなステップになります。

sliders メソッドのアーギュメントが有効かどうかチェックする。
もし、有効ならば..
要求されたスライダ数が、対応可能な範囲内(0 - 128)かどうか確認する。
この js オブジェクトによって既に作られているオブジェクトを取り除く。
新しいオブジェクトを作り、それら同士の接続を行う。
このjs オブジェクトを見つけ(後述)新しい funnel に接続する。
もし、有効でなければ
Max ウィンドウにエラーメッセージを表示し、関数を抜ける
この JavaScript コードでは、条件文 (if…else…) と 繰り返し(for() loops) という、手続き型プログラミングの重要な2つの機能を使っています。C や Java のような他のプログラミング言語を使ったことがあれば、これらの構文はおなじみのはずです。詳しくは、JavaScript リファレンスを参照するとよいでしょう。
 
sliders() 関数では、まず最初に、パッチャーから送られた slider メッセージのアーギュメントのチェックを行っています。これは、関数自身の引数(アーギュメント)のプロパティをチェックすることによって行います。例えば

if(arguments.length) { // 実行されるコード }

によって、関数を呼び出したメッセージのアーギュメントの数が 0 でない場合のみ、中括弧の間のコードが実行されます。そうでない場合、この部分のコードは無視されます。同様に、アーギュメントに対し、配列として添字によってアクセスすることができます。

a = arguments[0];

上の例では、変数にメッセージの第1アーギュメントに格納された値を割当てます。サンプルプログラムでは、この方法によって、作成するスライダの数を参照しています。

オブジェクトの生成とメンテナンス

Max で js を使ってオブジェクトの生成を行う場合、Maxobj クラスをオブジェクト変数として使い、オブジェクトの生成、接続、消滅を行うことができます。これは、まず Max パッチの JavaScript 表現である Patcherオブジェクトにアクセスすることによって行います。 次の文

this.patcher.remove(thefunnel)

は、this (これは、常に該当の js を含むパッチャーを表します)というPacher の中の thefunnnelという Maxobj を探し、それを削除するようjs に命じるものです。文の中の‘this’は実際にはオプションですが、JavaScript によって、該当の js オブジェクトが存在するパッチだけではなく、それ以外のパッチにあるオブジェクトをコントロールすることもできることは覚えておく価値があります。

オブジェクトを生成するためには、Patcher によって作られる新しい Maxobj に変数を割り当てます。

thefunnel = this.patcher.newdefault(300, 300, “funnel”, a);

このケースでは、Maxobj thefunnel が、デフォルトのオブジェクトとしてパッチャーウィンドウの 300 300という座標位置に作られます。オブジェクトのクラスは funnel に設定され、オブジェクトのアーギュメントには変数 a が持つ値がセットされます。

注:Patcher オブジェクトの newdefault() メソッドは、ちょうど、手作業でパレットやパッチャーのコンテキストメニューからオブジェクトを作る場合と同じように新しいオブジェクトを生成します。これによって、thispatcher オブジェクトに対して使うメッセージを、かなり簡略化できます。オブジェクトの全てのパラメータ(オブジェクトの幅、フラグ等)を指定したい場合には、この代わりに newobject() メソッドを使うことができます。

接続は、2つのMaxobj を指定し、Patcher オブジェクトの connect() メソッドを使ってリンクさせることによって行います。例えば、

this.patcher.connect(thesliders[5], 0, thefunnel, 5)

という文の場合、配列 thesliders の中の6番目の Maxobj の最も左 (0) のアウトレットを thefunnel という Maxobj の6番目のインレットに接続します。配列の添字もインレットやアウトレットのナンバーも 0 から始まることを思い出して下さい。

一度に複数のオブジェクトを生成するために、繰り返し(ループ)と配列を使います。例えば、

for(k=0;k<8;k++) { controlin[k] = this.patcher.newdefault(300+(k*50), 50,“ctlin”, k+1); thesliders[k] = this.patcher.newdefault(300+(k*50), 100,“uslider”); this.patcher.connect(controlin[k], 0, thesliders[k], 0); this.patcher.connect(thesliders[k], 0, thefunnel, k); }

という例では、自動的に8個の ctlin と uslider オブジェクトを、パッチャーウィンドウ上に(水平の座標位置 300 からスタートして)50 ピクセルの間隔を空けて作り、お互いを接続し、その後、thefunnel で参照される funnel オブジェクトに接続します。この JavaScript コードで、変数 k が宣言されていない点に注意して下さい。これは、k が(この sliders() 関数の中だけで)ローカル変数としてのみ使われ、関数が呼び出されるたびに再初期化されるためです。この章のチュートリアルパッチにある実際のコードでは、8 という数値はローカル変数 a に置き換えられています。この a は作りたいスライダの数を表すものです。

this の中から自分自身を見つける

sliders() の中で行わなければならない重要な点の1つに、JavaScript によって生成された funnel オブジェクトと、もともとの js オブジェクトのインレットとの接続があります。しかし、もともとの js オブジェクトは手作業によって作られたもので、JavaScript によって生成されたものではありません(これを行おうと考えても不可能です)。Maxobj をJavaScript プログラムとは別個に作られたオブジェクトに接続するにはどうすればよいのでしょうか?

ourself = this.box; // Maxobj をもともとの js オブジェクトに割り当てます

パッチャー の‘box’プロパティは、もともとのjs オブジェクトそれ自身を参照するMaxobjを返します!その後、ourself という変数を使って、それにもともとのjs オブジェクトを代入します。これにより、JavaScript コードに含まれるオブジェクトに接続することが可能になります。

新しく代入された Maxobj である ourself を使って、funnel オブジェクトともともとのjs オブジェクトを接続します。

this.patcher.connect(thefunnel, 0, ourself, 0);

その他のメソッド

このチュートリアルの js オブジェクトは、MIDI コントロール装置を作るだけではありません。このオブジェクトは、コントロール装置からのメッセージに応答するだけでなく、Maxパッチャーからの他のメッセージに対しても応答します。チュートリアルパッチの js オブジェクト用に書かれたソースコードを再び開き、list() という関数を見つけて下さい。

// list -- 生成された funnel オブジェクトから読み取ります。 function list(val) { if(arguments.length==2) { thevalues[arguments[0]] = arguments[1]; } }

sliders() 関数と同様、list() 関数も、最初に、Max から送られてくる値の数をチェックします。次の行がそれです。

if(arguments.length==2) {}

funnel オブジェクトは、値を受け取ったインレットのナンバーの後に、受け取った値を続けたリストを出力します。例えば、2番目のインレット(実際にはこのインレットのインレットナンバーは 1 になります)に 55 という数値が送信されると、funnel オブジェクトからは、1 55 というリストが出力されます。list関数では2つの値を使うため、list()メソッドの処理を続ける前に、メッセージが2つのアーギュメントを持っていることを確認するためのチェックを行います。第 1 のアーギュメント(どのスライダが動かされたか)を使って、配列 thevalues の何番目の要素に第2 のアーギュメント(値)を代入するかを決定します。

JavaScript コードの中の、bang() および reverse() 関数を見て下さい。

// bang -- ステップスルーシーケンサ function bang() { // シーケンサのリセット if(seqcounter>=numsliders) { seqcounter = 0; } if(thereverse) // 配列から逆方向に読み込みます。 { // シーケンス内の位置を送信。 outlet(1, numsliders-seqcounter-1); // カレント(現時点)のノートを送信。 outlet(0, thevalues[numsliders-seqcounter-1]); } else // 配列から順方向に読み込みます。 { // シーケンス内の位置を送信。 outlet(1, seqcounter); // カレント(現時点)のノートを送信。 outlet(0, thevalues[seqcounter]); } seqcounter++; // シーケンスをインクリメント。 } // reverse -- シーケンスの方向を変更します。 function reverse(val) { if(arguments.length) { thereverse = arguments[0]; // 方向を反転。 } }

bang() メソッド(パッチでは、metro オブジェクトによってトリガされています)は、counter オブジェクトと良く似た方法で値のシーケンスを読み通します。カウントの最大値は、パッチの中のスライダ数(numsliders によって定義されます)によってセットされます。カウントは常に増加し、スライダの数を超えると 0 に戻ります。reverse() 関数は、Max から送られる reverse メッセージのアーギュメントに基づいて、変数(thereverse)を設定します。これによって、bangメソッドが配列(thevalues)に格納されている値を読み取る方向が変更されます。格納されている値はslider オブジェクトによるコントローラから送られたものです。2つの outlet() 関数は、js オブジェクトの右(1)アウトレットから現在のインデックス値を出力し、その後、シーケンスの中のそのインデックスにあたる値をjs オブジェクトの左(0) アウトレットから出力します。この例で、アウトレットから値を出力する場合に、重要な Max の仕様である、「右から左への順序(Right to Left Order)」に従っている点に注意して下さい。そうでないと、pack オブジェクトは、右インレットに入力されるべき値を受け取る前に、左インレットへの入力によってトリガされてしまいます。

outlet() 関数は、シーケンスの中の現在のインデックスの位置にある値を js オブジェクトの左(0) アウトレットから出力します。

JavaScript がどのように動作しているかがわかったと思いますので、もう少しこのパッチを動かしてみて下さい。標準のMax オブジェクト table と counter を使ってこのシーケンサを再現する方法を考えてみて下さい。

結び

js オブジェクトは、JavaScript によって動的にMax パッチを生成する強力な手段を提供します。オブジェクトの生成は、Patcher オブジェクトによって作られる Maxobj オブジェクトを変数に代入することによって行われます。newdefault() および newobject() メソッドによって、オブジェクトを生成することができ、それを remove() メソッドによって消滅させることができます。connect() メソッドを使うと、スクリプトの中で Maxobj 間のパッチ接続を行います。パッチャーの「box」プロパティを通して、js オブジェクト自身に Maxobj を割当てることができます。Max メッセージに応答するメソッドとして動作するJavaScript 関数を設計する場合、メッセージと共に渡されるアーギュメントは、関数内のアーギュメント配列を通して取得することができます。

コードリスト

// autosurface.js // // 視覚的なフィードバックを行なうMIDIコントロール(スライダ)を自動的に生成し、 // それを funnel オブジェクトに接続して、簡単なシーケンサを動作させます。 // // rld, 5.04 // // インレットとアウトレット inlets = 1; outlets = 2; // グローバル変数と配列 var numsliders = 0; var seqcounter = 0; var thereverse = 0; var thevalues = new Array(128); // スクリプトのためのMaxobj 変数 var controlin = new Array(128); var thesliders = new Array(128); var thefunnel; // ここからメソッドの記述になります。 // sliders -- Max パッチの中でスライダを生成し、接続します。 function sliders(val) { if(arguments.length) // アーギュメントがない場合、処理を行いません。 { // アーギュメントの構文分析 a = arguments[0]; // スライダ数のセーフティチェック if(a<0) a = 0; // 0 の場合:スライダが少なすぎる if(a>128) a = 128; // 128 の場合:スライダが多すぎる // 古いものを取り外します。 // 一度処理を行っている場合、funnel を取り除きます。 if(numsliders) this.patcher.remove(thefunnel); // 以前の sliders の値を使って ctlin および slider オブジェクトを // 取り除きます。 for(i=0;i<numsliders;i++) { this.patcher.remove(controlin[i]); this.patcher.remove(thesliders[i]); } // 新しいものを作ります。 numsliders = a; // グローバル変数として格納する sliders の数を新しい値に // 更新します。 if(numsliders) // funnelを作ります。 thefunnel = this.patcher.newdefault(300, 300, "funnel", a); // ctlin slider オブジェクトを作り、それらをお互いに接続し、 // 同時にfunnnel と接続します。 for(k=0;k<a;k++) { controlin[k] = this.patcher.newdefault(300+(k*50), 50,“ctlin”, k+1); thesliders[k] = this.patcher.newdefault(300+(k*50), 100,“uslider”); this.patcher.connect(controlin[k], 0, thesliders[k], 0); this.patcher.connect(thesliders[k], 0, thefunnel, k); } // 新しいオブジェクトをこの js オブジェクトのインレットに接続します。 ourself = this.box; // この js オブジェクトに Maxobj を割当てます。 if (numsliders) // funnel js に接続します。 this.patcher.connect(thefunnel, 0, ourself,0); } else // アーギュメントについて説明します。 { post(“sliders message needs arguments”); post(); }
} // list -- 生成された funnel オブジェクトから読み取ります。 function list(val) { if(arguments.length==2) { thevalues[arguments[0]] = arguments[1]; } } // bang -- ステップスルーシーケンサ function bang() { // シーケンサのリセット if(seqcounter>=numsliders) { seqcounter = 0; } if(thereverse) // 配列から逆方向に読み込みます。 { // シーケンス内の位置を送信。 outlet(1, numsliders-seqcounter-1); // カレント(現時点)のノートを送信。 outlet(0, thevalues[numsliders-seqcounter-1]); } else // 配列から順方向に読み込みます。 { // シーケンス内の位置を送信。 outlet(1, seqcounter); // カレント(現時点)のノートを送信。 outlet(0, thevalues[seqcounter]); } seqcounter++; // シーケンスをインクリメント。 } // reverse -- シーケンスの方向を変更します。 function reverse(val) { if(arguments.length) { thereverse = arguments[0]; // 方向を反転。 } }

参照

js Max の JavaScript オブジェクト