yoppa.org


メディア芸術演習 VI - メディア・アート II

openFramewoks – OSC (Open Sound Control) を利用したネットワーク連携

今回は、ネットワークを使用したopenFrameworks同士の連携と、openFrameworksと他のアプリケーションとの連携について取り上げます。openFrameworksでネットワークを利用す方法はいくつかありますが、今回はその中で比較的扱い易いプロトコルである、Open Sound Control (OSC) についてとりあげたいと思います。

Open Sound Control (OSC)とは

Open Sound Controlは、カリフォルニア大学バークレー校にある CNMAT(The Center for New Music and Audio Technologies)が開発した、通信プロトコルです。その特徴について、CNMATのページではOSCについて下記のように要約されています。

Open Sound Control (OSC) is a protocol for communication among computers, sound synthesizers, and other multimedia devices that is optimized for modern networking technology. Bringing the benefits of modern networking technology to the world of electronic musical instruments, OSC’s advantages include interoperability, accuracy, flexibility, and enhanced organization and documentation.

(訳) Open Sound Control (OSC) は、コンピュータ、シンセサイザー、その他のマルチメディアデバイス同士でコミュニケーションするための通信プロトコルです。現代のネットワーク技術の成果を電子楽器の世界に適用することで、OSCは、相互運用性、正確さ、柔軟さ、また、拡張性に優れた性能を持ちます。

この説明からわかるように、OSCは当初は電子楽器を連携する目的で開発されました。すっかり古くなってしまったMIDI(Musical Instrument Digital Interface)の次世代を担うプロトコルとなることを狙いとしています。その通信の仕組みとして、インターネットで用いられている通信方式(TCP/IP、UDP/IP)を活用することで、柔軟な連携を可能にしています。

現在では、OSCはその簡易さと柔軟性から電子楽器やコンピュータ音楽の範囲を越えて、様々なアプリケーションやハードウェアに実装されています。主なものだけでも以下のリストのように数多くのアプリケーションやハードウェアで実装されています。

サウンド系アプリケーション

ビジュアル系アプリケーション

ハードウェア

OSCのプロトコル

OSCのプロトコルは大きくわけて2つのパートに分けられます。一つは、OSCメッセージ(OSC Message)、もう一つはOSC引数(OSC Arguments)です。

OSCメッセージは、送受信するOSCの情報内容をラベリングしたものです。つまり、ここでOSCの値が何を意味してるのかを表しています。OSCメッセージは、WWWなどのインターネットアプリケーションで用いられる、URL (Uniform Resorce Locator) と良く似ています。メッセージは階層構造を持つことが可能で、その階層をスラッシュ「/」で区切って表現します。例えば、マウスの状態をOSCで送受信する際に、その座標と、マウスボタンの状態の2つのメッセージを送りたい場合には、その両者を「mouse」というメッセージでまとめて、その下位メッセージとしって「position」と「button」というメッセージがあると考えます。すると、マウスの座標は「/mouse/position」マウスのボタンの状態は「/mouse/button」というメッセージとして両者を階層的に表現できます。このOSCメッセージはアプリケーションでOSCを使用する目的に応じて自由にデザインしていくことが可能となっています。

OSC引数 (OSC Argument) は実際の値を送信します。値は、整数(int32)、実数(float)、文字列(string)など様々な型を送受信することが可能です。また、複数の値を一度に送受信することも可能となっています。

KeynoteScreenSnapz001.png

OSCプロトコルの中身

osc_address.png

OSCメッセージのネームスペースの例

openFrameworksのアプリケーション同士でOSCを送受信する

では、まず初めにopenFrameworksのアプリケーション同士で、OSCを送受信してみましょう。まずは簡単なサンプルとして、マウスの位置とマウスボタンの状態をOSCで表現してみましょう。

OSCは、マウスの位置と、マウスボタンの状態で2種類使用することにします。それぞれ以下のようにメッセージをわりふります。

  • マウスの位置
    • メッセージ:/mouse/position
    • 引数:X座標 Y座標 (例: 320 240)
  • マウスボタンの状態
    • メッセージ:/mouse/button
    • 引数:ボタンの状態 (“up”, “down”)

openFrameworksのプログラムは、送信側(Sender)と受信側(Receiver)の2種類で構成されます。それぞれのプログラムでOSCを使用するためには、ofxOscアドオンをプロジェクトに追加する必要があります。追加した状態は下記のようになります。

それでは、受信側=サーバー(oscReceiveExample)、送信側=クライアント(oscSenderExample)それぞれをプログラムしていきましょう。

サーバー側 (oscReceiveExample)

testApp.h

#ifndef _TEST_APP
#define _TEST_APP


#include "ofMain.h"
#include "ofxOsc.h"

//ポート番号を設定
#define PORT 8000

//--------------------------------------------------------
class testApp : public ofBaseApp{
    
public:
    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dumpOSC(ofxOscMessage m); //OSCメッセージを出力
    
private:
    //OSCメッセージを受信するインスタンス
    ofxOscReceiver	receiver;
    //マウス座標
    int remoteMouseX, remoteMouseY;
    //マウスボタンの状態 ("up", "down")
    string mouseButtonState;
    //string oscString;
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
    //指定したポートで接続
	receiver.setup( PORT );
    
    //値を初期化
	mouseX = 0;
	mouseY = 0;
	mouseButtonState = "";

	ofBackground(0, 0, 0);
}

void testApp::update(){
	//現在順番待ちのOSCメッセージがあるか確認
	while( receiver.hasWaitingMessages() )
	{
        //次のメッセージを取得
		ofxOscMessage m;
		receiver.getNextMessage( &m );
        
        //マウスの位置を取得
		if ( m.getAddress() == "/mouse/position" ){
            remoteMouseX = m.getArgAsInt32( 0 );
			remoteMouseY = m.getArgAsInt32( 1 );
            
		} 
        //マウスボタンの状態を取得
        else if ( m.getAddress() == "/mouse/button" ) {
			mouseButtonState = m.getArgAsString( 0 ) ;
		}

        //OSCメッセージをそのままコンソールに出力
        dumpOSC(m);
    }
}

//OSCメッセージをコンソールに出力する関数
void testApp::dumpOSC(ofxOscMessage m) {
    string msg_string;
    msg_string = m.getAddress();
    for (int i=0; i<m.getNumArgs(); i++ ) {
        msg_string += " ";
        if(m.getArgType(i) == OFXOSC_TYPE_INT32)
            msg_string += ofToString( m.getArgAsInt32(i));
        else if(m.getArgType(i) == OFXOSC_TYPE_FLOAT)
            msg_string += ofToString( m.getArgAsFloat(i));
        else if(m.getArgType(i) == OFXOSC_TYPE_STRING)
            msg_string += m.getArgAsString(i);
    }
    cout << msg_string << endl;    
}

void testApp::draw(){
    int radius;
    if (mouseButtonState == "down") {
        //マウスボタンが押されていたら、赤い円を描画
        radius = 20;
        ofSetColor(255, 127, 0);
    } else {
        //マウスボタンが押されていなければ、青い円を描画
        radius = 10;
        ofSetColor(0, 127, 255);
    }
    ofCircle(remoteMouseX, remoteMouseY, radius);
}

void testApp::keyPressed  (int key){}

void testApp::mouseMoved(int x, int y ){}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){}

void testApp::mouseReleased(int x, int y, int button){}

void testApp::windowResized(int w, int h){}

クライアント側 (oscSenderExample)

testApp.h

#ifndef _TEST_APP
#define _TEST_APP

#include "ofMain.h"
#include "ofxOsc.h"

#define HOST "localhost" //送信先ホストのIPを設定
#define PORT 8000 //送信先のポート番号を設定

//--------------------------------------------------------
class testApp : public ofBaseApp{
    
public:
    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    
private:
    //OSCメッセージの送信者
    ofxOscSender sender;
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
	ofBackground(0, 0, 0);
    //指定したIPアドレスとポート番号でサーバーに接続
	sender.setup( HOST, PORT );
}

void testApp::update(){
}

void testApp::draw(){
    //現在のマウスの場所に円を描画
    ofSetColor(255, 255, 255);
    ofCircle(mouseX, mouseY, 10);
}

void testApp::keyPressed  (int key){}

void testApp::mouseMoved(int x, int y ){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/position" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
    //メッセージを送信
	sender.sendMessage( m );
}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/button" );
    //OSC引数として、マウス状態"down"を送信
	m.addStringArg( "down" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
	sender.sendMessage( m );
}

void testApp::mouseReleased(int x, int y, int button){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/button" );
    //OSC引数として、マウス状態"up"を送信
	m.addStringArg( "up" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
	sender.sendMessage( m );
}

void testApp::windowResized(int w, int h){}

oscSenderExampleDebugScreenSnapz001.png

送信用プログラムと受信用プログラムを起動して、送信側のウィンドウの上でマウスポインタを動かすと、受信側のプログラムで同じ動きが再現されます。

このプログラムは1台のコンピュータの中だけでなく、ネットワークに繋がったコンピュータ同士でOSCをやりとりすることが可能です。試しに隣の席のコンピュータのIPを調べて、送信側(oscSenderExample)のtestApp.hの7行目に記述してあるIP「”localhost”」を例えば「”192.168.1.10″」のように隣の席のIP番号で置き換えてみましょう。こうすることでネットワークを介して繋がったコンピュータ同士でOSCメッセージが送られていることを確認できます。

受信側のプログラムは、通信の状態をコンソールから確認することができます。XCodeメニューから「実行」→「コンソール」を選択してコンソール画面を表示した状態で実行すると、下記のようにOSCメッセージが受信されていることを確認できます。

複数で参加できるアプリケーション

次にもう少し複雑なサンプルとして、一つのサーバーに複数のクライアントからメッセージを送ることのできるアプリケーションを作成してみましょう。

クライアント側で画面をクリックすると、サーバー側の同じ場所から輪が波紋のように拡がっていくアプリケーションを作成してみましょう。拡がっていく円は別のクラスRingとして作成しています。クライアント側の参加者全員が同じIP番号を指定することで、サーバに向って複数のクライアントから同時にOSCメッセージを送ることが可能です。

サーバー側

testApp.h

#ifndef _TEST_APP
#define _TEST_APP


#include "ofMain.h"
#include "ofxOsc.h"
#include "Ring.h"

//ポート番号を設定
#define PORT 8000

class testApp : public ofBaseApp{
    
public:
    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dumpOSC(ofxOscMessage m);
    vector <Ring *> rings; //拡大する輪"Ring"の配列
    
private:
    ofxOscReceiver	receiver;
    int remoteMouseX, remoteMouseY;
    string mouseButtonState;
    string oscString;
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
    //指定したポートで接続
	receiver.setup( PORT );
    ofSetFrameRate(60);
    
    //値を初期化
	mouseX = 0;
	mouseY = 0;
	mouseButtonState = "";

	ofBackground(0, 0, 0);
}

void testApp::update(){
	//現在順番待ちのOSCメッセージがあるか確認
	while( receiver.hasWaitingMessages() )
	{
        //次のメッセージを取得
		ofxOscMessage m;
        oscString = m.getAddress();
		receiver.getNextMessage( &m );
        
        //マウスの位置を取得
		if ( m.getAddress() == "/mouse/position" ){
            remoteMouseX = m.getArgAsInt32( 0 );
			remoteMouseY = m.getArgAsInt32( 1 );
            
		} 
        //マウスボタンの状態を取得
        else if ( m.getAddress() == "/mouse/button" ) {
			mouseButtonState = m.getArgAsString( 0 ) ;
            remoteMouseX = m.getArgAsInt32( 1 );
			remoteMouseY = m.getArgAsInt32( 2 );
		}

        //OSCメッセージをそのままコンソールに出力
        dumpOSC(m);
    }
    
    //マウスアップされたら、新規にRingを追加
    if(mouseButtonState == "up"){
        rings.push_back(new Ring(ofPoint(remoteMouseX, remoteMouseY)));
        mouseButtonState = "";
    }
    
    //Ring更新
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end();){
        (*it)->update();
        if ((*it)->dead) {
            delete (*it);
            it = rings.erase(it);
        } else {
            ++it;
        }
    }
}

//OSCメッセージをコンソールに出力する関数
void testApp::dumpOSC(ofxOscMessage m) {
    string msg_string;
    msg_string = m.getAddress();
    for (int i=0; i<m.getNumArgs(); i++ ) {
        msg_string += " ";
        if(m.getArgType(i) == OFXOSC_TYPE_INT32)
            msg_string += ofToString( m.getArgAsInt32(i));
        else if(m.getArgType(i) == OFXOSC_TYPE_FLOAT)
            msg_string += ofToString( m.getArgAsFloat(i));
        else if(m.getArgType(i) == OFXOSC_TYPE_STRING)
            msg_string += m.getArgAsString(i);
    }
    cout << msg_string << endl;    
}

void testApp::draw(){
    //Ringを描画
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end(); ++it){
        (*it)->draw();
    }
}

void testApp::keyPressed  (int key){}

void testApp::mouseMoved(int x, int y ){}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){}

void testApp::mouseReleased(int x, int y, int button){
    rings.push_back(new Ring(ofPoint(x, y)));
}

void testApp::windowResized(int w, int h){}

Ring.h

#ifndef _RING
#define _RING

#include "ofMain.h"

class Ring {
    
public:
    Ring(ofPoint pos); //コンストラクタ
    void update(); 
    void draw();
    
    ofPoint pos; //輪の中心位置
    float radius; //輪の半径
    float radiusSpeed; //輪の拡大スピード
    bool dead; //生死の判定

private:

};

#endif

Ring.cpp

#include "Ring.h"

Ring::Ring(ofPoint _pos)
{
    pos = _pos;
    radius = 0;
    radiusSpeed = 0.5;
    dead = false;
}

void Ring::update()
{
    radius += radiusSpeed;
    if (radius > ofGetWidth()) {
        dead = true;
    }
}

void Ring::draw()
{
    ofSetCircleResolution(64);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    ofEnableSmoothing();
    ofSetLineWidth(1);
    ofPushMatrix();
    ofTranslate(pos.x, pos.y);
    ofNoFill();
    ofSetColor(63, 127, 255, 127 - radius * 127 / ofGetHeight());
    ofCircle(0, 0, radius);
    ofFill();
    ofSetColor(63, 127, 255, 31 - radius * 31 / ofGetHeight());
    ofCircle(0, 0, radius);
    ofPopMatrix();
}

クライアント側

testApp.h

#ifndef _TEST_APP
#define _TEST_APP

#include "ofMain.h"
#include "ofxOsc.h"
#include "Ring.h"

#define HOST "192.168.1.10" //サーバのIPアドレスを指定する
#define PORT 8000

//--------------------------------------------------------
class testApp : public ofBaseApp{
    
public:
    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    vector <Ring *> rings; //輪の動的配列
    
private:
    //OSCメッセージの送信者
    ofxOscSender sender;
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
	ofBackground(0, 0, 0);
    ofSetFrameRate(60);
    //指定したIPアドレスとポート番号でサーバーに接続
	sender.setup( HOST, PORT );
}

void testApp::update(){
    //Ring更新
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end();){
        (*it)->update();
        if ((*it)->dead) {
            delete (*it);
            it = rings.erase(it);
        } else {
            ++it;
        }
    }
}

void testApp::draw(){
    //Ringを描画
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end(); ++it){
        (*it)->draw();
    }
}

void testApp::keyPressed  (int key){}

void testApp::mouseMoved(int x, int y ){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/position" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
    //メッセージを送信
	sender.sendMessage( m );
}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/button" );
    //OSC引数として、マウス状態"down"を送信
	m.addStringArg( "down" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
	sender.sendMessage( m );
}

void testApp::mouseReleased(int x, int y, int button){
    //OSCメッセージの準備
    ofxOscMessage m;
    //OSCアドレスの指定
    m.setAddress( "/mouse/button" );
    //OSC引数として、マウス状態"up"を送信
    m.addStringArg( "up" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
    m.addIntArg( x );
    m.addIntArg( y );
    sender.sendMessage( m );
    
    //Ringを追加
    rings.push_back(new Ring(ofPoint(x, y)));
}

void testApp::windowResized(int w, int h){}

※ Ring.hとRing.cppに関しては、サーバ側(oscRingReceiver)と同じ。

oscRingReceiverDebugScreenSnapz001.png

他のアプリケーションとの連携1:Pure Data (pd) と連携

「Open Sound Control とは」のセクションで解説したように、OSCはopenFrameworks以外にも多くのアプリケーションで実装され利用可能です。異なるアプリケーション同士を連携させることで、そのアプリケーションで得意な分野に特化させて、苦手な部分は他のアプリケーションに任せることが可能となります。例えばopenFrameworksで複雑な音響生成をするのはあまり向いていません。そこで、音響合成用のアプリケーションやDSP用の言語を用いて音響合成を行い、openFrameworksでは得意とする高速のグラフィック処理に専念するということが可能となります。

OSCが利用可能なアプリケーションは、Max/MSP、Pure Data、Csound、SuperCollider、ChucKなど数多く存在しています。今回は、Pure Data(pd)とSuperColliderをOpenFrameworksと連携させてみようと思います。

pdには、MillerPacketの開発した基本機能だけを収録したバージョン(pd-vanilla)と、様々なサードパーティーの機能拡張を含んだバージョン(pd-extended)があります。
pdでOSCを扱いたい際は、拡張機能を収録したpd-extendedを使用する必要があります。最新版のpd-extendedは、下記からダウンロードしてください。

PdでのOSCの受信は、とてもシンプルです。まず、「dumpOSC」オブジェクトを用いて、OSCを受信します。その際に第1引数としてポート番号を指定します。次に受信したOSCメッセージを、「OSCroute」オブジェクトで分解していきます。例えば、「/mouse/position」というOSCメッセージを取り出したい場合には、「OSCroute /mouse」として/mouse以下のメッセージを取り出した上で、「OSCroute /position」として/position以下の値を抽出します。あとは、パラメータの数に応じて「unpack」オブジェクトでOSC引数を分解します。

では、pdで簡単なFMシンセサイザーのパッチを作成してみましょう。そして、モジュレーターのオシレータの周波数とインデックスをそれぞれ、マウスのX座標とY座標でコントロールできるよう、OSCの受信側の設定をしています。マウスの座標は「/mouse/position」メッセージで受信するようになっています。

screen(2010-12-12 16.37.45).png

このpdのパッチにOSCメッセージを送出するopenFrameworksのプログラムは、openFrameworks同士でOSCを送受信していたものと全く同じやり方で、実現可能です。

testApp.h

#ifndef _TEST_APP
#define _TEST_APP

#include "ofMain.h"
#include "ofxOsc.h"
#define HOST "localhost" //IPアドレスを入力
#define PORT 8000

//--------------------------------------------------------
class testApp : public ofBaseApp{
    
public:
    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    
private:
    //OSCメッセージの送信者
    ofxOscSender sender;
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
	ofBackground(0, 0, 0);
    //指定したIPアドレスとポート番号でサーバーに接続
	sender.setup( HOST, PORT );
}

void testApp::update(){}

void testApp::draw(){
    //現在のマウスの場所に円を描画
    ofSetColor(255, 255, 255);
    ofCircle(mouseX, mouseY, 10);
}

void testApp::keyPressed  (int key){}

void testApp::mouseMoved(int x, int y ){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/position" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
    //メッセージを送信
	sender.sendMessage( m );
}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/button" );
    //OSC引数として、マウス状態"down"を送信
	m.addStringArg( "down" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
	sender.sendMessage( m );
}

void testApp::mouseReleased(int x, int y, int button){
    //OSCメッセージの準備
	ofxOscMessage m;
    //OSCアドレスの指定
	m.setAddress( "/mouse/button" );
    //OSC引数として、マウス状態"up"を送信
	m.addStringArg( "up" );
    //OSC引数として、現在のマウスの座標(x, y)を送信
	m.addIntArg( x );
	m.addIntArg( y );
	sender.sendMessage( m );

}

void testApp::windowResized(int w, int h){}

まず、Pd側のプログラムを起動してDSPをONにした状態で、openFrameworks側のプログラムを実行すると、マウスの位置によって音色が変化するFM合成された音響が生成されます。openFramworks同士の連携と同様、IPアドレスを設定することで、別のマシンから遠隔操作することも可能です。

PdをopenFrameworksプログラム内部に格納する – ofxPd

openFrameworksとPdをOSCを介して組み合わせる方法として、より便利なアドオンofxPdが開発されています。このアドオンを使用すると、Pd側のパッチでは、dumpOSCやOSCrouteなどを使用することなく、シンプルに「r パラメータ名」という形式で値を取得することが可能となります。

また、ofxPdを使用すると、Pdのプログラムを実行することなくopenFrameworksのプログラム内部にPdの音響合成機能を内包することが可能となります。ofxPdでpdのパッチを使用するには、作成したパッチをopenFrameworksのプロジェクトフォルダの「bin/data/」フォルダ内に格納します。

では、ofxPdで使用できるように、先程作成したFM合成のパッチに変更を加えます。変更点は、パッチを開いて200ミリ秒後に自動的に音響合成を開始できるようにしている部分と、パラメータを「dumpOSC」を経由することなく「r」オブジェクトから直接読み込むようにしている部分です。

screen(2010-12-12 17.31.02).png

openFrameworks側では、ofxOSCに加えてofxPdとofxThredをアドオンとしてプロジェクトに追加する必要があります。

アドオンの設定が完了したら、下記のプログラムを作成します。

testApp.h

#ifndef _TEST_APP
#define _TEST_APP

#include "ofMain.h"
#include "ofxPd.h"
#include "ofxOsc.h"

class testApp : public ofBaseApp{
    
public:
    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    
    void audioRequested(float * output, int bufferSize, int nChannels);
    void audioReceived(float * input, int bufferSize, int nChannels );
    
    float * audioInputData;
    
    // Pdオブジェクト
    ofxPd pd;
    
    // Pdと通信するためのOSC
    ofxOscSender osc_sender;
    
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
	ofSetFrameRate(60);
	ofBackground(0, 0, 0);

	//OSCの初期化
	static const string HOST = "localhost";
	static const int PORT = 8000;
	osc_sender.setup( "localhost", PORT);
	
	// pdのための定数を定義
	// 出力と入力のチャンネル数
	static const int NUM_OUT_CHANNELS = 2;
	static const int NUM_IN_CHANNELS = 2;
	// サンプリングレイト
	static const int BITRATE = 44100;
	// バッファーサイズ
	static const int BUFFER_SIZE = 256;
	// 使用するバッファーの数
	static const int NUM_BUFFERS = 4;
	// Pdのブロックサイズ
	static const int PD_BLOCK_SIZE = 64;

    // Pdを初期化
	pd.setup( "", NUM_OUT_CHANNELS, NUM_IN_CHANNELS, BITRATE, PD_BLOCK_SIZE );
	// Pdファイルを読み込み
	pd.addOpenFile( "simple_fm.pd" );
	// Pdを開始
	pd.start();
	// オーディオ入力を定義
	audioInputData = new float[BUFFER_SIZE*NUM_IN_CHANNELS];
	// サウンド出力を初期化
	ofSoundStreamSetup( NUM_OUT_CHANNELS, NUM_IN_CHANNELS, this, BITRATE, BUFFER_SIZE, NUM_BUFFERS );
	// Pdから音を出力
	pd.startDSP();
}

void testApp::update(){}

void testApp::draw(){
    ofSetColor(0, 127, 255);
    ofCircle(mouseX, mouseY, 20);
}

void testApp::keyPressed  (int key){
}

void testApp::keyReleased  (int key){
}

void testApp::mouseMoved(int x, int y ){
	// マウスの座標をPdのパラメータとして送出
	pd.sendFloat( "modulator_freq", x );
	pd.sendFloat( "modulator_index", y );
}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){}

void testApp::mouseReleased(int x, int y, int button){}

void testApp::windowResized(int w, int h){}

void testApp::audioRequested(float * output, int bufferSize, int nChannels){
    //Pdの音を計算
	pd.renderAudio( audioInputData, output, bufferSize, nChannels );
}

void testApp::audioReceived(float * input, int bufferSize, int nChannels){
	memcpy(audioInputData, input, bufferSize*nChannels*sizeof(float));
}

openFramwroksのプログラムを実行すると、Pdを起動することなく音響が合成されます。openFrameworksのプログラムの中にPdが内包された状態になっています。このようにopenFrameworks単体に機能を統合することで、実際に作品内で使用する際にセットアップを容易で確実に行うことが可能となるでしょう。

openFrameworksとSuperColliderの連携 – ofxSuperCollider

次にopenFrameworksとSuperColliderを連携させてみましょう。

SuperColliderは、言語部分と音響合成部分が分離していて、その相互の連携にOSCを用いています。ですので、その仕組み自体がとてもOSCを用いた他のプログラムとの連携に適しています。具体的には、SuperColliderで楽器を定義する「SynthDef」という関数を使用して、楽器定義ををSuperColliderの音響合成サーバに格納します。あとは、openFrameworksからSuperColliderで行うのと同じ方法で、OSCのメッセージを送出することで音響を合成することが可能となります。

openFrameworksとSuperColliderの連携は、ofxOSCを用いてOSCの素のメッセージを送信しても可能ですが、SuperColliderとの連携に特化したアドオンofxSuperColliderを用いるとより簡単に連携を行うことが可能です。ofxSuperColliderを使用するには、ofxSuperColliderの他に、ofxOscが必要となります。

以下のサンプルは、画面上でマウスをクリックすると、SuperColliderで定義した楽器”newRing”を生成して演奏します。また、生成された音響全体にリバーブをかける楽器”reverb”も同時に用いています。

SuperColliderの楽器定義 (sc_inst.scd)

// SynthDef
(
SynthDef("reverb", {
	arg wet=1.0;
	var in, fx;
	in = In.ar(0, 2);
	fx = in;
	fx = GVerb.ar(fx, 80);
	ReplaceOut.ar(0, fx);
}).store;

SynthDef("baseSound", {
	arg note=40, amp=0.1, fadein=12.0;
	var env, out;
	env = EnvGen.kr(Env.new([0, amp], [fadein]));
	out = RLPF.ar(LFPulse.ar([note, note+7].midicps, 0.15), SinOsc.kr(0.1, 0, 10, 72).midicps, 0.1, 0.1);
	Out.ar(0, out*env);
}).store;

SynthDef("newRing", {
	arg note=40, amp=0.5, pan = 0.0, decay=4.0;
	var env1, out1, env2, out2, mix;
	out1 = RLPF.ar(LFPulse.ar([note, note+7].midicps, 0.15), SinOsc.kr(0.1, 0, 10, 72).midicps, 0.1, 0.1);
	out2 = SinOsc.ar([(note+48).midicps, (note+55).midicps]);
	env1 = EnvGen.kr(Env.perc(decay/4.0, decay/4.0*3.0, amp, -4), doneAction: 2);
	env2 = EnvGen.kr(Env.adsr(0.001, 0.4, 0.0, decay, amp*0.1, -4));
	mix = (out1 * env1) + (out2 * env2);
	mix = CombN.ar(mix, 0.31, 0.31, 2, 0.5, mix);
	Out.ar(0, mix);
}).store;
)

// test
s.sendMsg("/s_new", "reverb", x = s.nextNodeID, 1, 1);
s.sendMsg("/s_new", "baseSound", x = s.nextNodeID, 1, 1);
s.sendMsg("/s_new", "newRing", x = s.nextNodeID, 1, 1, "note", 42);

testApp.h

#ifndef _TEST_APP
#define _TEST_APP


#include "ofMain.h"
#include "ofxSuperCollider.h"
#include "Ring.h"

class testApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    
    ofxSCSynth *reverb; // SC楽器 "reverb"
    ofxSCSynth *newRing; // SC楽器 "newRing"
    ofxSCSynth *baseSound;  // SC楽器 "baseSound"
    vector <Ring *> rings; //拡大する輪"Ring"の配列
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
    ofSetFrameRate(60);
    ofBackground(0, 0, 0);
    
    reverb = new ofxSCSynth("reverb");
    reverb->create();
    baseSound = new ofxSCSynth("baseSound");
    baseSound->create();
}

void testApp::update(){
    //Ring更新
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end();){
        (*it)->update();
        if ((*it)->dead) {
            delete (*it);
            it = rings.erase(it);
        } else {
            ++it;
        }
    }
}

void testApp::draw(){
    //Ringを描画
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end(); ++it){
        (*it)->draw();
    }
}

void testApp::keyPressed(int key){}

void testApp::keyReleased(int key){}

void testApp::mouseMoved(int x, int y ){}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){}

void testApp::mouseReleased(int x, int y, int button){
    //newRingの楽器を新規に生成し演奏
    int note[8] = {28, 35, 40, 47, 52, 59, 64, 71};
    newRing = new ofxSCSynth("newRing");
    newRing->set("note", note[int(ofRandom(0, 8))]);
    newRing->set("pan", (x - ofGetWidth() / 2.0) / ofGetWidth() * 2.0);
    newRing->create();
    
    //Ringを追加
    rings.push_back(new Ring(ofPoint(x, y)));
}

void testApp::windowResized(int w, int h){}

Ring.h

#ifndef _RING
#define _RING

#include "ofMain.h"

class Ring {
    
public:
    Ring(ofPoint pos);
    void update();
    void draw();
    
    ofPoint pos;
    float radius;
    float radiusSpeed;
    bool dead;
    
private:

};
#endif

Ring.cpp


#include "Ring.h"

Ring::Ring(ofPoint _pos)
{
    //初期設定
    pos = _pos;
    radius = 0;
    radiusSpeed = 0.5;
    dead = false;
}

void Ring::update()
{
    //輪の半径を拡大
    radius += radiusSpeed;
    //もし画面の幅より半径が大きくなったら、死亡
    if (radius > ofGetWidth()) {
        dead = true;
    }
}

void Ring::draw()
{
    //輪を描く
    ofSetCircleResolution(64);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    ofEnableSmoothing();
    ofSetLineWidth(2);
    ofPushMatrix();
    ofTranslate(pos.x, pos.y);
    ofNoFill();
    ofSetColor(63, 127, 255, 200);
    ofCircle(0, 0, radius);
    ofPopMatrix();
}

oscRingReceiverDebugScreenSnapz001.png

このプログラムで音を生成するには、まずSuperColliderを起動し、サーバーを起動します。その上で楽器の定義を選択して「Enter」キーを押して楽器定義をサーバーに格納します。その後、openFrameworks側でプログラムを実行し、画面をクリックすると、拡がる輪とともに、SCで生成した音響が生成されます。

SuperColliderの楽器定義をopenFrameworksに内包する – ofxSuperColliderServer

SuperColliderの楽器もまた、openFrameworksのプログラムに内包することが可能です。SuperColliderをopenFrameworksに内包するには、ofxSuperColliderServerというアドオンを使用します。このアドオンは楽器定義ファイルをopenFrameworksのプログラムに直接読み込んでSuperColliderによる音響合成を可能とするとても便利なアドオンです。使用方法もとても簡単で、ofxSuperColliderServerのアドオンを読み込んだ後初期化を行うだけで、あとはofxSuperCllider単体で行った際と同じ方法でプログラムします。

先程のプログラムを書き換えて、openFramework単体で音響合成が可能なようにしてみましょう。

testApp.h

#ifndef _TEST_APP
#define _TEST_APP


#include "ofMain.h"
#include "ofxOsc.h"
#include "Ring.h"
#include "ofxSuperCollider.h"
#include "ofxSuperColliderServer.h"

//ポート番号を設定
#define PORT 8000

//--------------------------------------------------------
class testApp : public ofBaseApp{
    
public:    
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dumpOSC(ofxOscMessage m);
    vector <Ring *> rings; //拡大する輪"Ring"の配列
    ofxSCSynth *reverb; // SC楽器 "reverb"
    ofxSCSynth *newRing; // SC楽器 "newRing"
    ofxSCSynth *baseSound;  // SC楽器 "baseSound"
    
private:
    ofxOscReceiver	receiver;
    int remoteMouseX, remoteMouseY;
    string mouseButtonState;
    string oscString;
};

#endif

testApp.cpp

#include "testApp.h"

void testApp::setup(){
    //指定したポートで接続
	receiver.setup( PORT );
    ofSetFrameRate(60);
    
    //値を初期化
	mouseX = 0;
	mouseY = 0;
	mouseButtonState = "";

	ofBackground(0, 0, 0);
    
    ofxSuperColliderServer::init();
    reverb = new ofxSCSynth("reverb");
    reverb->create();
    baseSound = new ofxSCSynth("baseSound");
    baseSound->create();
}

void testApp::update(){
	//現在順番待ちのOSCメッセージがあるか確認
	while( receiver.hasWaitingMessages() )
	{
        //次のメッセージを取得
		ofxOscMessage m;
        oscString = m.getAddress();
		receiver.getNextMessage( &m );
        
        //マウスの位置を取得
		if ( m.getAddress() == "/mouse/position" ){
            remoteMouseX = m.getArgAsInt32( 0 );
			remoteMouseY = m.getArgAsInt32( 1 );
            
		} 
        //マウスボタンの状態を取得
        else if ( m.getAddress() == "/mouse/button" ) {
			mouseButtonState = m.getArgAsString( 0 ) ;
            remoteMouseX = m.getArgAsInt32( 1 );
			remoteMouseY = m.getArgAsInt32( 2 );
		}

        //OSCメッセージをそのままコンソールに出力
        dumpOSC(m);
    }
    
    //マウスアップされたら、新規にRingを追加
    if(mouseButtonState == "up"){
        rings.push_back(new Ring(ofPoint(remoteMouseX, remoteMouseY)));
        mouseButtonState = "";
        
        //SCで音を鳴らす
        int note[8] = {28, 35, 40, 47, 52, 59, 64, 71};
        newRing = new ofxSCSynth("newRing");
        newRing->set("note", note[int(ofRandom(0, 8))]);
        newRing->set("pan", (remoteMouseX - ofGetWidth() / 2.0) / ofGetWidth() * 2.0);
        newRing->create();
    }
    
    //Ring更新
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end();){
        (*it)->update();
        if ((*it)->dead) {
            delete (*it);
            it = rings.erase(it);
        } else {
            ++it;
        }
    }
}

//OSCメッセージをコンソールに出力する関数
void testApp::dumpOSC(ofxOscMessage m) {
    string msg_string;
    msg_string = m.getAddress();
    for (int i=0; i<m.getNumArgs(); i++ ) {
        msg_string += " ";
        if(m.getArgType(i) == OFXOSC_TYPE_INT32)
            msg_string += ofToString( m.getArgAsInt32(i));
        else if(m.getArgType(i) == OFXOSC_TYPE_FLOAT)
            msg_string += ofToString( m.getArgAsFloat(i));
        else if(m.getArgType(i) == OFXOSC_TYPE_STRING)
            msg_string += m.getArgAsString(i);
    }
    cout << msg_string << endl;    
}

void testApp::draw(){
    //Ringを描画
    for(vector <Ring *>::iterator it = rings.begin(); it != rings.end(); ++it){
        (*it)->draw();
    }
}

void testApp::keyPressed  (int key){}

void testApp::mouseMoved(int x, int y ){}

void testApp::mouseDragged(int x, int y, int button){}

void testApp::mousePressed(int x, int y, int button){}

void testApp::mouseReleased(int x, int y, int button){
    rings.push_back(new Ring(ofPoint(x, y)));
    int note[8] = {28, 35, 40, 47, 52, 59, 64, 71};
    newRing = new ofxSCSynth("newRing");
    newRing->set("note", note[int(ofRandom(0, 8))]);
    newRing->set("pan", (x - ofGetWidth() / 2.0) / ofGetWidth() * 2.0);
    newRing->create();
}

void testApp::windowResized(int w, int h){}

サンプルプログラムのダウンロード

今回紹介した全てのプログラムは、下記からダウンロード可能です。