超意訳OSC with SuperCollider

OSC_communication

プログラム間のOSCコミュニケーションは、あるアプリから他のアプリにメッセージを送ることでやりとりされる。違うコンピュータでアプリが走っていてもおk。OSCの基礎はUDP/TCPだよ。
SuperColliderではターゲットアプリのNetAddrと他のアプリからメッセージを受信するOSCresponderNodeを作ってコミュニケートするよ。

他のアプリにOSCメッセージを送るよ

他のアプリとコミュニケーションを確立させるには、他のアプリがどのポートでメッセージを受け取るのか知っておく必要があるよ。
例えばさ、7771ポートでリスンしているアプリがあるとするよ。そんな時は、下のようにNetAddrつくってメッセージ送るんだ。

	b = NetAddr.new( "127.0.0.1", 7771 );
	b.sendMsg( "/helle", "there" );
他のアプリからOSCメッセージを受け取るよ

SCが他のアプリからメッセージを受け取るときは、他のアプリはSuperColliderのリスニングポートにメッセージを送らなくちゃダメなんだ。
SCのデフォルトポートは57120だ。でもたまに違うよ、SCを立ち上げたとき既に使われちゃっていた時とかね。
今のデフォルトポートは下で取得できるから確認してね。

	NetAddr.langPort;

IPアドレスも一緒に取得したいならこっちだ。

	NetAddr.localAddr;

送られてきたメッセージを受け取るには、SC側でOSCresponderを作成しなくちゃならないんだ。
メッセージ送信側アプリがメッセージを送る固定ポートを持っているなら、OSCresponderでそのIPアドレスとポートからメッセージを受け取るように設定するんだ。これは、メッセージ送信アプリが7771番ポートにメッセージ送信しているときの例だよ。

	n = NetAddr.new( "127.0.0.1", 7771 );
	o = OSCresponderNode.new( n, "/goodbye", 
		{
			arg time, resp, msg;
			[ time, resp ].postln;
		}
	).add;
	o.remove;

なんでOSCresponderじゃなくて、OSCresponderNodeかって?同じメッセージ名('/aaa'とか)を受け取るOSCresponderを作ったとすると、2個目のOSCrespoderは1個目を上書きしちゃうんだ。同時に1メッセージあたり1つのOSCresponderしか作成できないってことだね。例えば、下の方でサンプルがあるけれど'/tr'を受け取る時とかは複数作成する場合があるよ。
でもOSCresponderNodeはそんな制限はないんだ!同じ名前のOSCresponderNodeを共存させられるんだ。
(もういらなくなった時にOSCresponderNodeを消すには作ったOSCresopnderNodeをずっと追跡していなくちゃいけないってことでもあるね)詳しくはOSCpathResponderをみ・て・ね!

複数ポートからのメッセージを受け取るよ

固定ポートにメッセージを送らないアプリもあるよね、問題ない、どのポートからでもメッセージを受け取れるようにOSCresponderNodeを設定できるんだ。
OSCresponderNodeの第一引数としてNetAddrを指定する代わりに、nilだ。

	o = OSCresponderNode.new( nil, "/goodbye", 
		{
			arg time, resp, msg;
			[ time, resp ].postln;
		]
	).add;
	o.remove;
受信トラフィックのテスト

全て受信したOSCメッセージはrecvOSCmessageかrecvOSCbundleを呼ぶんだ。受信トラフィックを見るには、recvOSCfuncを使おう。(何やっているのかよくわからないねw)

	(
		thisProcess.recvOSCfunc = {
			arg time, addr, msg;
			if( msg[0] != 'status.reply' )
				{ "time: % sender: %\nmessage: %\n".postf( time, addr, msg ); }
		}
	);
	thisProcess.recvOSCfunc = nil;

OSCresponderNode

OSCresponderNode

あるOSCアドレスからあるコマンドを受信したときに呼び出される関数を設定する、それがOSCresponderNodeだ。OSCresponderのように同じインターフェースがあるけれども、OSCresponderNodeは同じコマンドに対して複数のレスポンスが許可されているんだ。

システムプロセスでOSCresponderNodeが関数を評価することに気をつけるんだ!アプリケーションプロセスにアクセスするためには、deferメッセージを使うんだ。GUIのアクセスみたいにね。(なに言ってんのこれ?)

SCにメッセージを送るアプリでは、SCサーバ/SClangに送るメッセージを区別させなくちゃならない。サーバメッセージはServer-Command-Referenceに書いてあるし、SCサーバのポートに送らなきゃダメなんだ。(デフォルト値が57110でs.addr.portだね)SCサーバに送られてきたメッセージはSClang内のどのOSCresponderでも処理されないんだ。後述するけれど、SynthDefで直接OSCメッセージの送受信ができないってことなんだ。

OSCresponderで処理される外部クライアントからのOSCメッセージは、デフォルト値が57120のSClangのポートに送らなくちゃいけない。どのポートがリスン状態かはNetAddr.langPortで確認できるよ。

OSCresponderNode設定
	/*
		受信側設定
	*/
	//NetAddr.new( hostname, port ) newメッセージは省略できるよ!
	n = NetAddr( "127.0.0.1", 57120 );
	
	//OSCresponderNode.new( addr, cmdName, action )
	o = OSCresponderNode( n, '/chat', 
		{
			arg t, r, msg;
			( "time:" + t ).postln;
			msg[1].postln;
		}
	).add;
	
	/*
		送信側設定
	*/
	//NetAddr.new( hostname, port )
	m = NetAddr( "127.0.0.1", 57120 )
	
	//sendMsg( args… ) send a message without timestamp to the addr.
	//第一引数でOSCコマンドを指定して、第二引数のメッセージをOSCコマンドに送るよ。
	m.sendMsg( "/chat", "Hello App 1" );
	
	//sendBundle(timestamp, args… ) send a bundle with timestamp to the addr. 
	//タイムスタンプと一緒に複数メッセージを送るよ。詳しくはServerのヘルプみてね。
	(
		m.sendBundle( 2.0, [ "/chat", "hello app 1" ], [ "/chat", "hello world" ] );
		m.sendBundle( 0.0, [ "/chat", "hello app 1" ], [ "/chat", "hello world" ] );
	)
	
	//disconnect;	close TCP connection.
	//remove; remove and deactivate the OSCresponder
	//もう使わないから捨てるよ
	m.disconnect;
	n.disconnect; o.remove;
どんなクライアントからもデータ受け取るよ

前述したけれど、第一引数にnil

ポートはなんでもいいけれど、特定ホストからのみメッセージ受け取るよ
	//NetAddrのポートを指定せずに、nilにするんだ
	n = NetAddr( "127.0.0.1", nil );
	o = OSCresponderNode( n, '/test', { | t, r, msg | msg.postln; } ).add;
	o.remove;
SCサーバからのメッセージを受け取るよ

synthdefは一旦SCサーバに保存して、SCサーバで読み込まれるんだけれど、SCサーバから直接OSCメッセージの送受信はできないんだ。だから一旦OSCresponderNodeで受信しているんだよ。外部アプリにOSCメッセージを送りたい時は、OSCresponderNode内の関数でsendMsgしてあげると上手くいくよ。
ここでのsynthdefは'/tr'にDust.krタイミングで0.9の値を送っているだけのシンセだ。OSCresponderNodeで'/tr'に送られてきたメッセージを受信しているのが分かるかな。

	(
		s = Server.local.boot;
		s.notify;
	)
	(
		SynthDef( "help-SendTrig", 
		{
			SendTrig.kr( Dust.kr( 1.0 ), 0, 0.9 );
		}).send( s );
		
		//これなんだけれども、OSCresponderで定義すると上書きされちゃうから"this is another call"しか出力されないんだ
		a = OSCresponderNode( s.addr, '/tr', { | t, r, msg | [ t, r, msg ].postln; }).add;
		b = OSCresponderNode( s.addr, '/tr', { | t, r, msg | "this is another call".postln; }).add;
		
		x = Synth.new( "help-SendTrig");
	)

いじょう

ヘルプは不親切ですなあ。なんか間違っている気もするけれど気にしない。
OSCBundleはまだ使わないので、あとはOSCpathResponder知っておけばいいのかな。