ローカルエリアにおいてコンピュータによるどのようなアンサンブルが可能であるかに関する実験は、前述の通り、 The League of Automatic Music Composers 及び The HUB などによって行なわれてきた。彼らによる様々な作品において多く見られる傾向として、個々のコンピュータにできる限り対等な役割を与えようとしているように思われる。それによって、コンピュータ間にインタラクティブな関係を築くことが可能となり、それこそが彼らの主な関心の対象であった。
しかし、複数のコンピュータの間に何らかの関係を持たせようとする場合に、まず考えられるのは、相互的で対等であるということよりも、一方のコンピュータで他方のコンピュータを一定の範囲で制御するということである。そのための代表的なプロトコルがMIDIであった。その意味において、The HUBによるMIDIの応用はかなり例外的であり、当然ながら、実験的なものであった。
恐らくMIDIが登場した際に最も画期的であったのは、複数の電子楽器による演奏において、1つのアンサンブルとしてリズムを同期させることが可能になったことであろう。これをSuperColliderでコンピュータ間のアンサンブルとして、どのように実現させることができるだろうか。
道具となるオブジェクトはSendTrigとOSCresponderである。まずはヘルプファイルを読むことにする。 SendTrigには以下のように書かれている。
「トリガ(0からゼロではない値への移行)を受け、クライアントに向けてサーバからトリガメッセージを送り返す。」
ここで「サーバ server」と「クライアント client」という関係が現れてくる。
「Serverオブジェクトは、サーバappをクライアント側で表現したものであり、SuperColliderランゲージ・アプリケーションからそのサーバappを制御するために用いられるものである。」
「. . . 、SC3はその操作をランゲージ・アプリケーション(SCを起動するためにダブルクリックする SuperCollider app)とシンセシス=サーバ・アプリケーション(scsynthというUNIXのコマンドラインによるア プリケーションで、[ランゲージ・アプリケーションの]起動時にデフォルトで作られるようになっている『ローカル』サーバのウィンドウ上にあるブート・ボタンを押した時、あるいはコードからServerをブートした時に起動する)との間で分割している。この2つのアプリケーションは、CNMATによって開発されたOpenSound Controlのサブセッ トを用い、UDPないしはTCPを 通して互いに情報を伝達し合う。」
以上から分かるのは、ここで言われている「クライアント」とは、要するにランゲージ・アプリケーションを意味しているということである。SendTrigによってトリガを送るのは、そのSendTrigを含むSynthDefが送られ、それによって作られるsynthのノードを司るサーバであり、トリガが送られるのは、そのサーバを同じコンピュータ上 から制御するランゲージappとなる。通常は指示を与える側であるクライアントがこの場合にはサーバからのトリガ を受ける側になるため、SendTrigのヘルプファイルでは「クライアントへ送り返す send . . . back to the client」と表現されていたのである。
SendTrigのヘルプファイルに用例として挙げられていたものは、サーバappとランゲージappとが同じコンピュータ上のものとして扱われていたが、これを2つのコンピュータで行なうこともできる。
まずは、2台のコンピュータをハブを通してLAN接続するか、クロスケーブルで直接繋げるかし、ネットワークの設定を行なう。ここでは、192.168.0.1と192.168.0.2の2つをIPアドレスとして用いる。
まず、片方のコンピュータ(master)が用いるコードは以下の通りである。
// 相手のコンピュータ。
t = Server.new(\remote, NetAddr(“192.168.0.2”, 57110));
// 4秒毎にトリガが送られるようにする。
SynthDef(“trig”, { SendTrig.kr(Impulse(0.25), 0, 1) }
).send(t); // 定義を相手のコンピュータへ送る。
// トリガを送る。
t.sendMsg(“/s_new”, “trig”, x = t.nextNodeID, 1, 1);
そしてもう一方のコンピュータ(slave)が用いるコードは次のようになる。
s = Server.local; // こちら(クライアント)のIPアドレスは192.168.0.2
s.boot;
OSCresponder(
s.addr, // サーバからのメッセージを受けるようにする。
'/tr', // SendTrigから受けるOSCメッセージのコマンド名。
{arg time, responder, msg;
[time, responder, msg].postln;
} // ここで引数となっているものを画面に出力する。
).add;
この場合には、OSCresponderのactionを単なる引数の画面への出力にしているが、この部分を音響の出力に関係する処理にすれば、片方のコンピュータのリズムをもう一方のコンピュータのリズムに同期させることができる。
SendTrigによって送られるトリガは、'/tr'というコマンド名でOSCresponderによって受けることになるが、トリガ以外のデータ、つまり'/tr'以外のコマンド名を持つデータでも、OSCresponderによって受けることができる。その場合に、データを送るものとしてはNetAddrオブジェクトを使う。
メッセージを送る側の一般的な形は、
NetAddr(t.addr.hostname, 57120).sendMsg(“message”, 任意のデータ);
// ここではtがメッセージを受ける側のコンピュータを指す。
// ポート番号は57120を使用する。
となり、受ける側のOSCresponderは、
OSCresponder(
t.addr.port_(57120),
// ここではtがメッセージを送る側のコンピュータ
// を指す。ポート番号は57120。デフォ ルトでは
// 57110になっているので、変更が必要。
“message”, // コマンド名はmessage。
{arg time, responder, msg;
. . . // msg.at(1)で「任意のデータ」を取り出すことができる。
}
).add;
となる。
SendTrigの例とは違い、OSCresponderの一番目の引数はメッセージを送ってくる相手のコンピュータのNetAddrとなる。
こうした仕組みを使った、2台のコンピュータによる合奏の例を以下に挙げる。
ここでは、片方のコンピュータ(master)がメロディを奏で、もう一方のコンピュータ(slave)がそのメロディに合わせ て別のメロディを奏でるようにしたい。そして、masterとなるコンピュータによって、両方のコンピュータのテンポを制御できるようにする。
まずはmasterから。
s = Server.local;
// slave
t = Server.new(\remote, NetAddr("192.168.0.2", 57110));
s.boot;
(
var timeValue;
// テンポを制御するためのGUIを作る。
w = SCWindow("tempo_control", Rect(100, 200, 300, 60));
w.view.decorator = FlowLayout(w.view.bounds);
EZSlider(
w,
250@20,
"tempo",
ControlSpec(80, 160, \lin, 1),
{arg ez;
~tempo = ez.value;
s.sendMsg("/n_set", 1000, "tempo", ~tempo/60);
NetAddr(t.addr.hostname, 57120).sendMsg("tempo", ~tempo)
},
120
);
w.front;
// テンポの初期設定
~tempo = 120;
//トリガと音のためのsynth定義を両方のサーバに送る。
SynthDef("trig", {arg tempo;
SendTrig.kr(Impulse.kr(tempo), 0, 1)
}).send(s);
SynthDef("trig", {arg tempo;
SendTrig.kr(Impulse.kr(tempo), 0, 1)
}).send(t);
SynthDef("sound", {arg freq;
Out.ar(
0,
SinOsc.ar(freq.midicps, 0, 0.1)
*EnvGen.kr(Env.perc, levelScale: [0.5, 0.5], doneAction: 2)
)
}).send(s);
SynthDef("sound", {arg freq;
Out.ar(
0,
SinOsc.ar(freq.midicps, 0, 0.1)
*EnvGen.kr(Env.perc, levelScale: [0.5, 0.5], doneAction: 2)
)
}).send(t);
// トリガを受けて音を繰り出す。
OSCresponder(
s.addr,
'/tr',
{ arg time, responder, msg;
r.stop;
r = Routine({
loop({
timeValue = 60/~tempo;
s.sendMsg(
"/s_new",
"sound",
s.nextNodeID + 1,
1,
1,
"freq",
[59, 60, 64, 65, 67].choose
);
(timeValue*[1, 1, 0.5, 0.25].choose).wait;
})
});
r.play;
}
).add;
// トリガを両方のサーバ上に作る。
s.sendMsg("/s_new", "trig", 1000, 1, 1, "tempo", ~tempo/60);
t.sendMsg("/s_new", "trig", 1000, 1, 1, "tempo", ~tempo/60);
);
slaveは以下のようになる。
s = Server.local;
t = Server.new(\remote, NetAddr("192.168.0.1", 57110)); // master
s.boot;
(
var timeValue;
// テンポの初期設定
~tempo = 120;
// トリガを受けて音を繰り出す。
OSCresponder(
s.addr,
'/tr',
{ arg time, responder, msg;
r = Routine({
loop({
timeValue = 60/~tempo;
s.sendMsg(
"/s_new",
"sound",
s.nextNodeID,
1,
1,
"freq",
[71, 72, 76, 77, 79].choose
);
(timeValue*[1, 1, 0.5, 0.25].choose).wait;
})
});
r.play;
}
).add;
// テンポのデータを受け、それを反映させる。
OSCresponder(
t.addr.port_(57120),
"tempo",
{arg time, responder, msg;
~tempo = msg.at(1)
}
).add;
);
こうしたローカルエリアでの利用によって、1台のコンピュータの能力を超えるような処理も、複数のコンピュータを使って行なうことができるようになる。コンピュータの性能が飛躍的に向上しているとはいっても、技術的な面で作り手の想像力を完全に満足させることはあり得ない。限界は常に存在する。それが意識できない作り手は、無自覚なまま経済の機構に飼い馴らされているに過ぎない。かと言って、高性能な機械が生産されるのを受動的に待ち、やがてそれを消費するというのも、作り手の態度としては消極的過ぎるだろう。本当の意味で実験的な作品とは、経済の領域と正しく対峙したものだと思われる。