Max の js オブジェクトを使うと、JavaScript コードによって、パッチャー内でMax オブジェクトを動的に生成し、それに対してプロパティを設定し、メッセージを送り、オブジェクト間の接続を行うようなパッチャースクリプトを実行することが可能になります。JavaScript では、thispatcher オブジェクトにメッセージを送ることによって行うのは難しいと思われる方法でも、手続き的なコードによってパッチャーのエレメントを生成することが可能になります。このチュートリアルでは、パッチャーから送られてくるカスタムメッセージを扱うためのメソッドの使い方を紹介すると同時に、JavaScript によって書かれたカスタムメソッドによって Max パッチの中でのオブジェクトの生成、消滅や、オブジェクト間の接続を行う方法について説明します。
スクリプトに関する基本的な情報の多くはチュートリアル46:「基本的なスクリプト」で紹介しています。このチュートリアルを進める前に、そこで説明されている情報を復習しておきたいと思う場合には参照して下さい。
前の章の JavaScript に関するチュートリアルの時と同じように、チュートリアルパッチによって生成されるMIDI 情報を聴くために、MIDI シンセサイザ装置の接続についての復習をしてみようと思うかもしれません。チュートリアル12 :「MIDIノートの送受信」では、これらの接続の設定やテストの方法について説明しています。
・最初に チュートリアルパッチ 49.Javascripts Scripting.pat を開くと、大きな何もないパッチャーの空間が広がり、パッチャーウィンドウの下の方に js オブジェクトがあるのがわかると思います。js オブジェクトは、'autosurface.js' というJavaScript のソースファイルを読み込んでいます。このソースファイルはチュートリアルパッチと同じフォルダに置かれています。
js オブジェクトは(makenote 及び noteout オブジェクトを使って)MIDI 出力デバイスに数値を送るように設定されています。また、右アウトレットを持ち、ここから pack オブジェクトの右インレットへ、multislider オブジェクトを動かすためのメッセージを送っています。さらに、この js オブジェクトはそのインレットへ接続される多くのオブジェクトを持っています。metro オブジェクトが js オブジェクトに接続され、sliders n 及び reverse n というメッセージを送信する2つのメッセージボックスが接続されています。このn はそれぞれの場所で、接続されたナンバーボックスによって提供される値になります。
訳注:sliders n と size n の誤りではないかと思われます
パッチのレイアウトから、このjs オブジェクトのJavaScript コードには、bang、sliders、reverse、の少なくとも3つのメソッドがなければならないと推測することができます。実際にはもう1つのメソッドを持っていますが、それは、このパッチを使うことによって明らかになるでしょう。
・sliders $1 というメッセージを持つメッセージボックスに接続されているナンバーボックスを選択して下さい。数値の入力、またはスクロールによって値を 5 にして、何が起きるかを見て下さい。ナンバーボックスの数値を変更してみて下さい。大きな数(50 位)に設定してみて下さい。これを 0 に設定して、何が起きるかを見て下さい。
sliders $1 メッセージに応じて、js オブジェクトは、スクリプトによって動的にMax オブジェクトを生成し、接続します。このとき、ctlin と uslider オブジェクトのペアを、js オブジェクトへのメッセージを通じて要求した sliders の数と同じだけ作ります。さらに、uslider オブジェクトの数に見合うだけのインレットを持つ funnel オブジェクト作り、それらの間を接続します。その後 funnel オブジェクトは js オブジェクトに接続され、スライダによって生成された値は、同様にJavaScript コードによって使うことができるようになります。
・スライダを生成する際に、インクリメント(順に増加)していくMIDI コントローラナンバーの情報を待ち受けるために、 ctlin オブジェクトに自動的にナンバーが振られている点に注意して下さい。結果として、複数のコントローラーナンバーにMIDIの連続したコントロール値を送るMIDI コントロール装置は、複数の uslider オブジェクトに値を送ります。また、スライダの数を減らした場合、余分なオブジェクトが消える(実際に、すべて消滅し、再び作られています)点にも注意して下さい。sliders の値を 0 にセットすると、全てのスクリプトによって作られたオブジェクト(funnel も含みます)はパッチから削除されます。
・スライダの数を 5 程度の控えめな数に設定して下さい。uslider オブジェクトの中をクリックするか、MIDI コントローラ入力を使って、値を変更して下さい。metro オブジェクトに接続された toggle をクリックして、metro をオンにして下さい。uslider オブジェクトの値が順番に js オブジェクトから出力され、MIDI ノートのシーケンスを作ります。
パッチの右側にある multislider オブジェクトは、シーケンスに対応する位置を使って、シーケンサからのカレント(現在時点)のノートアウトを連続して表示します。
・reverse $1 と書かれたメッセージボックスに接続された toggle をクリックして下さい。uslider の値がシーケンスされる順序が逆方向になる点に注目して下さい。multislider も同様に逆方向に表示するようになります。
簡単に言えば、この js オブジェクトは動的に、測定可能な MIDI コントロール操作装置を(ctlin と uslider オブジェクトによって)生成し、これらのオブジェクトの値を使って、シンプルなMIDI ステップシーケンサを作るものです。JavaScript によって作られるスライダの数が、シーケンスの長さを決定します。
両方の toggle オブジェクトをオフにして、シーケンスを停止させ、シーケンサによる転送を「foward(前進)」モードに戻して下さい。それでは、この js オブジェクトのためのコードを見ていきましょう。
・チュートリアルパッチの 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 | パッチ内の uslider オブジェクトの状態を反映した値の配列(下記参照)。パッチ内の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 | パッチ内の uslider オブジェクトを参照する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つの関数として行われます。
サンプルプログラムの 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);
// ctlin と uslider を 以前の sliders の数を使って取り除く
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 と uslider オブジェクトを作り、それら同士、および 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(これは、常に該当の js を含むパッチャーを表します)というPacher の中の thefunnnel という Maxobj を探し、それを削除するようjs に命じるものです。
this.patcher.remove(thefunnel)
文の中の‘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 は作りたいスライダの数を表すものです。
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つのアーギュメントを持っていることを確認するためのチェックを行います。2 番目のアーギュメント(値)を配列 thevalues の何番目の要素として代入するかを決定するために、最初のアーギュメント(どのスライダを動かしたか)を使います。
・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)に格納されている値を読み取る方向が変更されます。この格納されている値は uslider オブジェクトによるコントローラから送られたものです。
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 関数を設計する時、メッセージと共に渡されるアーギュメントは、関数内のアーギュメント配列を通して得ることができます。
次のチュートリアルでは、JavaScript によって、時間処理されるイベントのスケジューリングを扱う方法や、js オブジェクト自身へのアーギュメントを分析する方法、Max 環境と変数を共有する方法について見ていきます。
// 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); // ctlin と uslider を 以前の sliders の数を使って取り除く 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 と uslider オブジェクトを作り、それら同士、および 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 オブジェクト |
データ構造 | Max のデータ格納の方法 |
thispatcher | パッチャーへのメッセージの送信。 |
counter | 受け取ったbang メッセージを数え、そのカウントを出力。 |
table | 数値による配列の格納とグラフィカルな編集 |