TouchDesignerからopenFrameworksアプリを実行する(Windows)

# はじめに
## 対象者
openFrameworksでのプログラミング経験者で、TouchDesignerとoFを連携させたい方

## サンプルファイル
[こちら](https://www.dropbox.com/s/9wtpnxyqqks7xu7/TD_oF.zip?dl=0)からダウンロードしてください。
oFアプリについては、バイナリとソースコード、addons.makeファイルのみ入っています。
ソースを編集・ビルドする場合は、TD_oF_samplesフォルダをoFのappsフォルダ直下に配置し、Project Generatorでプロジェクトファイルを作成してください。

## 実行環境
- Windows 10 Home Version 1709
- TouchDesigner 099 COMMERCIAL 64-Bit Build 2017.14620
- oprnFrameworks 0.9.8 VS Release
- Spout v2.006

# TouchDesignerからoFで作成されたアプリを起動する

## subprocessでoFアプリを起動
まずは、`Button COMP`が押されたら`CHOP Execute DAT`のpythonスクリプトが実行され、`Table DAT`に書かれた`fullpath`の行を参照し、`subprocess.Popen`でoFアプリを起動するようにネットワークを構築します。
(参考 : https://docs.python.jp/3/library/subprocess.html)

![td00.jpg](https://qiita-image-store.s3.amazonaws.com/0/55235/7c6443a1-f511-1934-23c6-bb74fddc3e45.jpeg)

`Table DAT`に`arguments`と名前を付け、以下のように編集しました。

|   | 0        |1                              |
|:-:|:--------:|:-----------------------------:|
| 0 | fullpath | oFアプリの実行ファイルの絶対パス |

続いて、`CHOP Execute DAT`を以下のように編集します。

```python:chopexec1
# subprocessモジュールを読み込み
import subprocess

def onOffToOn(channel, sampleIndex, val, prev):
	# table DATを取得
	table = op('arguments')
	
	# アプリの絶対パスを取得
	fullpath = table['fullpath', 1].val

	# バックスラッシュをエスケープ付きのバックスラッシュに置換
	newPath = fullpath.replace('\\', '\\\\')

	# popenでoFアプリを起動
	proc = subprocess.Popen(newPath)
	
	return
```

## 実行結果

![gif00.gif](https://qiita-image-store.s3.amazonaws.com/0/55235/1e6cd0fe-7d14-719b-356e-5a0f56090880.gif)

## oFアプリ実行時に引数を渡す

oFアプリ実行時に、引数としてウィンドウの横幅、縦幅の値を渡すことで、アプリのウィンドウサイズを設定してみます。
`table DAT`に`windowWidth`, `windowHeight`の行を追加し、`Chop Execute DAT`内を一部修正します。

![td01.jpg](https://qiita-image-store.s3.amazonaws.com/0/55235/3c389f68-a251-34bc-4b7d-4a72fd7cc352.jpeg)

|   | 0            |1                              |
|:-:|:------------:|:-----------------------------:|
| 0 | fullpath     | oFアプリの実行ファイルの絶対パス |
| 1 | windowWidth  | oFアプリの横幅                 |
| 2 | windowHeight | oFアプリの縦幅                 |

```python:chopexec1
import subprocess

# 中略

def onOffToOn(channel, sampleIndex, val, prev):
	# table DATを取得
	table = op('arguments')
	
	# アプリの絶対パスを取得
	fullpath = table['fullpath', 1].val

	# バックスラッシュをエスケープ付きのバックスラッシュに置換
	newPath = fullpath.replace('\\', '\\\\')

	# windowWidthを取得
	windowWidth = table['windowWidth', 1].val

	# windowHeightを取得
	windowHeight = table['windowHeight', 1].val

	# 引数をひとつの配列にまとめる
	args = [newPath, windowWidth, windowHeight]

	# popenでoFアプリを起動
	proc = subprocess.Popen(args)

	return
```

oFアプリも一部修正を加えます。

```cpp:main.cpp
#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main(int argc, char * argv[]){ // 引数 argc, *argv[]を追加
	ofSetupOpenGL(1024,768,OF_WINDOW);
	ofRunApp(new ofApp(argc, argv));

}
```

```cpp:ofApp.h
#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		ofApp(int argc, char * argv[]); // コンストラクタを追加

		void setup();
		void update();
		void draw();

		// 以下略

};

```

```cpp:ofApp.cpp
// ofApp.cpp内に追加
ofApp::ofApp(int argc, char * argv[]) {
	if (argc == 3) { // 引数が3つの場合
		int windowWidth = ofToInt(argv[1]); // 第二引数(windowWidth)を取得
		int windowHeight = ofToInt(argv[2]); // 第三引数(windowHeight)を取得
		ofSetWindowShape(windowWidth, windowHeight); // ウィンドウサイズを変更
	}
}
```

`main.cpp`の引数`argc`でコマンドライン引数の個数、`*argv[]`でコマンドライン引数自体を取得することが可能です。
(参考 : https://msdn.microsoft.com/ja-jp/library/17w5ykft.aspx)
`ofApp`内に`argc`, `argv[]`を引数に持つコンストラクタを追加します。
コンストラクタ内で第二、第三コマンドライン引数を取得し、ウィンドウサイズを変更しています。

## 実行結果

![gif01.gif](https://qiita-image-store.s3.amazonaws.com/0/55235/87fb63f6-8fb3-6484-a440-909a14d433c9.gif)

#TouchDesignerから実行中のoFアプリを終了させる
## taskkillで実行中のoFアプリを終了
`Button COMP`がもう一度押されたら、oFアプリを終了させるように、`Chop Execute DAT`を修正します。

```python:chopexec
# subprocessモジュールを読み込み
import subprocess

# process IDを格納する変数
PID = -1

def onOffToOn(channel, sampleIndex, val, prev):
	# table DATを取得
	table = op('arguments')
	
	# アプリの絶対パスを取得
	fullpath = table['fullpath', 1].val

	# バックスラッシュをエスケープ付きのバックスラッシュに置換
	newPath = fullpath.replace('\\', '\\\\')

	# windowWidthを取得
	windowWidth = table['windowWidth', 1].val

	# windowHeightを取得
	windowHeight = table['windowHeight', 1].val

	# 引数をひとつの配列にまとめる
	args = [newPath, windowWidth, windowHeight]

	# popenでoFアプリを起動
	proc = subprocess.Popen(args)

	# 起動したoFアプリのprocess IDを格納
	global PID
	PID = proc.pid

	return

def onOnToOff(channel, sampleIndex, val, prev):
	
	# oFアプリ起動時に保存したprocess IDを取得し、taskkillでoFアプリを終了させる
	global PID
	subprocess.Popen("taskkill /pid " + str(PID) + " /f")
	PID = -1
	
	return
```

`Button COMP`を押してoFアプリを起動する際、アプリのProcess IDを取得しグローバル変数に保存します。
もう一度`Button COMP`を押した際に`taskkill`で先ほど保存したProcess IDを指定し、アプリを終了しています。

## 実行結果

![gif02.gif](https://qiita-image-store.s3.amazonaws.com/0/55235/ad9082f2-25b2-4b0a-12bc-1b960324f31d.gif)

# oFアプリからSpoutでTouchDesignerにテクスチャーを送る
## oFアプリ実行時に、TouchDesignerからSpoutのSender Nameを渡す
![td03.jpg](https://qiita-image-store.s3.amazonaws.com/0/55235/75bed1d4-d6d8-fb70-5e64-9609a5244416.jpeg)

まずはプロジェクトに`Syphon Spout In TOP`を追加。
次に`table DAT`に`spoutSenderName`の行を追加し、任意の名前を入力します。

|   | 0               |1                              |
|:-:|:---------------:|:-----------------------------:|
| 0 | fullpath        | oFアプリの実行ファイルの絶対パス |
| 1 | windowWidth     | oFアプリの横幅                 |
| 2 | windowHeight    | oFアプリの縦幅                 |
| 3 | spoutSenderName | SpoutのSender名               |

`Syphon Spout In TOP`のsendernameに`op('arguments')['spoutSenderName', 1].val`と入力し、
`CHOP Execute DAT`の`onOffToOn`を以下のように書き換えます。

```python:chopexec1

def onOffToOn(channel, sampleIndex, val, prev):
	# table DATを取得
	table = op('arguments')
	
	# アプリの絶対パスを取得
	fullpath = table['fullpath', 1].val

	# バックスラッシュをエスケープ付きのバックスラッシュに置換
	newPath = fullpath.replace('\\', '\\\\')

	# windowWidthを取得
	windowWidth = table['windowWidth', 1].val

	# windowHeightを取得
	windowHeight = table['windowHeight', 1].val

	# spoutSenderNameを取得
	spoutSenderName = table['spoutSenderName', 1].val

	# 引数をひとつの配列にまとめる
	args = [newPath, windowWidth, windowHeight, spoutSenderName]

	# popenでoFアプリを起動
	proc = subprocess.Popen(args)

	# 起動したoFアプリのprocess IDを格納
	global PID
	PID = proc.pid

	return
```

これでコマンドライン引数として、`spoutSenderName`をoF側に送れるようになりました。

## oFアプリからSpoutでテクスチャーを共有

ofxSpout2というaddonを使用します。
https://github.com/ThomasMSondrup/ofxSpout2

`spoutSenderName`をコマンドライン引数として受け取り、string変数に格納。
`spout.sendTexture()`関数で、毎フレーム更新したfboを、TouchDesignerから指定したspoutSenderNameで送信しています。

```cpp:ofApp.h
#pragma once

#include "ofMain.h"
#include "ofxSpout2Sender.h"

class ofApp : public ofBaseApp{

	public:
		ofApp(int argc, char * argv[]);

		void setup();
		void update();
		void draw();
		void exit();
		
		ofxSpout2::Sender spout;
		ofFbo fbo;
		string spoutSenderName = "";
		int windowWidth = 1024;
		int windowHeight = 768;
};
```

```cpp:ofApp.cpp
#include "ofApp.h"

//--------------------------------------------------------------
ofApp::ofApp(int argc, char * argv[]) {
	if (argc == 4) {
		windowWidth = ofToInt(argv[1]); // 第二引数(windowWidth)を取得
		windowHeight = ofToInt(argv[2]); // 第三引数(windowHeight)を取得
		spoutSenderName = argv[3]; // 第四引数(spoutSenderName)を取得
		ofSetWindowShape(windowWidth, windowHeight); // ウィンドウサイズを変更
	}
}

//--------------------------------------------------------------
void ofApp::setup(){
	fbo.allocate(windowWidth, windowHeight); // Fboを作成
}

//--------------------------------------------------------------
void ofApp::draw(){
	fbo.begin();
	ofClear(0);

	// アニメーションを描画

	fbo.end();

	fbo.draw(0, 0);
	
	// spoutでFboのテクスチャーを送信
	if (spoutSenderName != "") {
		spout.sendTexture(fbo.getTexture(), spoutSenderName);
	}
}

//--------------------------------------------------------------
void ofApp::exit() {
	spout.exit(); // ofxSpout2を終了
}
```

## 実行結果

![gif04.gif](https://qiita-image-store.s3.amazonaws.com/0/55235/9f830457-f43d-0e06-b3fd-e90567cff007.gif)

# oFアプリを不可視にする
## oFアプリのウィンドウを不可視にする

以下のように、`ofMain.cpp`を修正します。
コマンドライン引数が1つ以上ある場合、`ofGLFWWindowSettings.visible = false;`とすることで、アプリを不可視にしています。

```cpp:ofMain.cpp
#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main(int argc, char * argv[]){

	ofGLFWWindowSettings settings;
	settings.width = 1024;
	settings.height = 768;
	if (argc > 1) {
		settings.visible = false;
	}
	ofCreateWindow(settings);

	ofRunApp(new ofApp(argc, argv));

}
```

## oFアプリのコンソールを非表示にする

`ofMain.cpp`内に`#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")`を追加します。

```cpp:ofMain.cpp
#include "ofMain.h"
#include "ofApp.h"

#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") // 追加

//========================================================================
int main(int argc, char * argv[]){
	// 中略
}
```

## taskkill時にコンソールを表示させない

`CHOP Execute DAT`の`onOnToOff`内を以下のように修正します。

```python:chopexec1
def onOnToOff(channel, sampleIndex, val, prev):
	# subprocess.STARTUPINFOを設定
	info = subprocess.STARTUPINFO()
	info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
	info.wShowWindow = subprocess.SW_HIDE
	
	# oFアプリ起動時に保存したprocess IDを取得し、taskkillでoFアプリを終了させる
	global PID
	subprocess.Popen("taskkill /pid " + str(PID) + " /f", startupinfo=info)
	PID = -1
	
	return
```

## 実行結果

![gif05.gif](https://qiita-image-store.s3.amazonaws.com/0/55235/706fecbd-276e-dd8f-eddc-b23a8cdf1089.gif)