Edited at

oFでつくったアプリを複数起動してOSCで一括コントロールするときのコツ

More than 3 years have passed since last update.

スクリーンショット 2013-12-15 22.46.48.png

コツというか単にやりたかったことが上手く行ったのでメモ。


Macで複数アプリケーションの立ち上げ方

oFでつくったアプリ(XXX.app)を複数立ち上げたいと思って何度もダブルクリックしても、ひとつしか起動しない。

しかし、そこはターミナルを使えばOK.

openコマンドで一度立ち上げたアプリケーションも -n をつければ新しくインスタンスを作成して起動してくれます。

$ open -n XXX.app


ポートが被らないようにするコツ

複数立ち上げたoFアプリにOSCを使って外部アプリケーションとパラメータをやりとりしたい可能性もあります。

しかし、ポートを定数で指定するような処理にすると、先に起動したやつがポートを占領してしまい、2つ目以降立ち上げたやつはOSC送受信ができなくなってしまいます。

対策としては、起動した後からポート番号を指定する処理にすればOKかと思いますが、一度に複数立ち上げたい時にいちいち「起動→ポートの指定」を繰り返すのはちょっと面倒。

そこで、コマンドライン引数で起動時にポートを指定するようにすると便利です。


まず、main.cppを書き換えます。


main.cpp

#include "ofMain.h"

#include "testApp.h"

//========================================================================
int main(int argc, char * argv[]){ //<————カッコの中に追記
ofSetupOpenGL(1024,768,OF_WINDOW);
ofRunApp(new testApp(argv[1])); //<————カッコの中に追記

}


次に、testApp.hの方にコンストラクタを追記します。


testApp.h

class testApp : public ofBaseApp{

public:

testApp(const char *arg); // <—————————— コレ

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 dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
}


こんな感じですれば、コマンドラインから起動した時に1個の引数を取得できます。

で、testApp.cpp の方にコンストラクタの内容を追記すればいいのですが、ポートはint型で渡さなきゃならないので、charのポインタとして受け取ったコマンドライン引数を解読してint型にしてやる必要があります。

いろいろやり方がありそうですが、自分の場合とりあえずこうしました。

(変更 14.07.01: nariakiiwatani@githubさんのコメントに従ってofToIntを使う方法に変更しました)


testApp.cpp

testApp::testApp(const char *arg)

{
mReceivePort = ofToInt(arg);
}

ちなみに上記だとOSC受信ポートしか指定していませんが、送信もする場合は送信ホストも指定できるようにするといいと思います。


ターミナルからopenコマンドで引数を渡して起動する方法

さて、ビルドしてバイナリをつくって、いざターミナルからopen -n xxx.app [ポート番号]とかで起動するとエラーになります。

openコマンドで引数を指定してアプリケーションを起動する場合はファイル名のあとに--argsをつけて引数をつければOKです。

$ open -n XXX.app —args (ポート番号)

これで、たとえば、

$ open -n XXX.app —args 50001

$ open -n XXX.app —args 50002
$ open -n XXX.app —args 50003

というふうにやれば、それぞれ50001, 50002, 50003のポートを開いた同じoFアプリが起動するはずです。

あとは開いたポートの全てに一括でパラメータを送ったりすることで、複数起動した全てのoFアプリに対して一度にパラメータを送ることが出来ます。


14.01.10


追記: この方法でVJをやってきました。

以下はそのDMEOです。

http://www.youtube.com/watch?v=wHoGPOJPCBU


14.02.28


追記: こんなのもあるんですね。ofxFenster

https://vimeo.com/30901120

https://github.com/underdoeg/ofxFenster.git


15.12.19


追記: oF0.9.0からサポートされたマルチウィンドウでやってみる

openFrameworksもversion0.9.0 がリリースされ,ついにマルチウィンドウがサポートされました.

この記事でやとうとしていることが1つのプロジェクトで出来るようになったわけですね.

詳しい使い方は examples/events のなかにexampleあるのでそれを見ればわかりますが,実際にやってみたのでソースを貼っておきます.


main.cpp

#include "ofMain.h"

#include "ofxGui.h"

//------------------------------------------------------------------------------
/*
ControllerApp, 主にMainAppを操作するための変数とGUI専用
*/

//------------------------------------------------------------------------------

class MainApp;

class ControllerApp : public ofBaseApp
{
// MainAppをフレンドクラス.こうしておけばControllerAppの変数はMainAppで自由に使える
friend class MainApp;

ofParameterGroup parameters;
shared_ptr<ofxPanel> gui; //<---- なぜかofxPanelはポインタとして後でnewしないとevent関係で実行時エラー起こす
ofParameter<float> size;
ofParameter<ofColor> color;

public:
void setup()
{
gui = shared_ptr<ofxPanel>(new ofxPanel());
gui->setup("PARAMETERS");
gui->add(size.set("SIZE", 10, 1, 50));
gui->add(color.set("COLOR", 100, ofColor(0, 0), 255));
ofSetWindowShape(gui->getWidth(), gui->getHeight());
ofSetVerticalSync(false);
}

void draw()
{
gui->setPosition(0, 0);
gui->draw();
}
};

//------------------------------------------------------------------------------
/*
MainApp, メインでジェネって描画するクラス,今回はこれのインスタンスをいっぱい作って一括操作する
*/

//------------------------------------------------------------------------------

class MainApp : public ofBaseApp
{
const int myID;
const shared_ptr<ControllerApp> mControllerApp;

public:

// コンストラクタで上野ControllerAppのポインタを受け取ったり,インスタンス別の定数を指定したり,
MainApp(shared_ptr<ControllerApp> controllerAppPtr, int ID) : mControllerApp(controllerAppPtr), myID(ID)
{
}

void draw()
{
ofBackground(0);

// ControllerAppのポインタからGUIで操作する変数をもらっていろいろやる
ofSetColor(mControllerApp->color);
ofDrawCircle(ofGetWidth()/2, ofGetHeight()/2, mControllerApp->size);

ofSetWindowTitle("ID: " + ofToString(myID) + " fps: " + ofToString(ofGetFrameRate()));
}
};

//------------------------------------------------------------------------------
/*
main関数,ここで上のクラスをいっぱい生成する
*/

//------------------------------------------------------------------------------

int main()
{
ofGLFWWindowSettings settings;

// GUI用のControllerAppを1つ生成
settings.width = 240;
settings.height = 120;
settings.setPosition(ofVec2f(0, 600));
settings.resizable = false;
shared_ptr<ControllerApp> controllerApp( new ControllerApp );
ofRunApp(ofCreateWindow(settings), controllerApp);

// メインのウィンドウを20個生成してみる
vector<shared_ptr<ofAppBaseWindow> > appWindows;
for (int i = 0; i < 20; ++i)
{
settings.width = 240;
settings.height = 120;
settings.setPosition(ofVec2f((i % 5) * 240, (i / 5) * 140));
settings.resizable = true;
ofRunApp(ofCreateWindow(settings), shared_ptr<MainApp>(new MainApp(controllerApp, i)));
}

ofRunMainLoop();
}


全ての実装をmain.cppに書くというおおちゃくぶりですが,実際にはmain関数と各クラスごとにファイルを分けたほうがいいと思います.

実行してみた結果がこちら↓

Screenshot 2015-12-19 03.44.08.png

適当に円を描いたウィンドウを20個生成して,左下のGUIでサイズや色を一括操作しています.

ただ,気づいたのがこの方法だとFPS落ちますね.やっぱり各々プロセスが分かれてるわけではないからでしょうか.円描くだけでも20個もつくればFPS=17ぐらいに落ちてます.

ただ前述の複数起動してOSCでまとめる方法よりは実装も起動も断然ラクなので,2〜3個ウィンドウに分けたい時とかにはやっぱり便利だと思います.