TouchDesignerとpythonに触れる

この記事は，フラー株式会社 [Advent Calendar 2021](https://qiita.com/advent-calendar/2021/fuller-inc) の 14 日目の記事です。

13日目の記事は @furusax さんによる [フラーで採用しているWebアプリケーションフレームワーク Goa の紹介](https://furusax0621.hatenablog.com/entry/2021/12/13/000000) でした。

## はじめに
今回は（iOSのまとまった物を書くネタと余裕がなかったため、細々と下書きしていた） 
TouchDesigner と、python を加えた話を記事にします。（大分毛色を変えています）

## 背景
きっかけは数年前に足を運んだ ULTRA Music Festival という音楽フェスで（台場に築かれたステージ中央に立つDJの音楽（所謂EDM）を楽しむというイベントです）、ライブ特有の高揚感も相まって巨大ステージから放たれる映像と光が音と連動する演出に感銘を受け、一体どういう仕組でステージと音が同期しているのだろうと不思議に思っていました。
調べていくうちに、TouchDesigner というアプリケーションが使われていることを知りました。
![IMG_0320.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/de2e77c6-250d-ac2d-9762-e01935321bae.jpeg)
TouchDesigner は映像・音・3Dなどインタラクティブなコンテンツを作成できるそうです。（pythonも使えつつ、ビジュアルプログラミングができます）
今回は、新しい領域にも触れてみたかったので、VJコントローラー作成方法を調べました。
VJコントローラーは、主にDJブース後ろにあるスクリーンに映像を流す（演出する）ためのコントローラーです。

## やりたいこと
- 簡易的なVJコントローラーを作る
    - 複数の映像を DJ コントローラーのようにクロスフェーダーで切り替える
- pythonを用いる
    - 映像選択時にアーティスト名と曲名を表示する

## 完成
完成したVJコントローラー

- 画面下部の左右の映像をクリックして選択
- 真ん中にスライダーを置いてクロスフェードさせる
- 画面上部の左右は選択した映像を表示する
- 画面上部の中央は実際に流れる映像

<iframe width="560" height="315" src="https://www.youtube.com/embed/zS34V-yU_Ok" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## 開発
TouchDesigner はノードと呼ばれるプログラムの塊を繋ぎ合わせていきます。

#### 全体の構成
ざっくりと言いますと、ノードをどんどん繋げて組み合わせることで目的の物を作ることができます。
更に python 組み合わせることで複雑な処理を入れ込むことができるようです。

ノードをコンテナとして捉えてそのコンテナ内にノードを置いて…といった具合でノードを枝分かれさせていくようなイメージで作っていきます。
ある程度の処理は一つのノードにまとめています。
（ノードは左から右に向かって配置してプログラムしていきます）

##### 大元のノード「project1」（例えると、ここがスマホの画面のようなものです）
![スクリーンショット 2021-12-12 9.28.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/9a108db0-980e-9aed-84f6-ad82bf9135e8.png)
ノードの中に入ってみましょう。

##### 「project1」ノード内
ここで各ノードから出力された映像やボタン・スライダーをまとめて制御しています。

- 「container_L」「container_R」に左右それぞれ映像を埋め込んでいます
- 「slider1」のスライダーはクロスフェードさせるUI部品
- 「cross1」で「container_L」「container_R」の映像を繋いで「out1」に出力
- 「textArtist_L」「textSongTitle_L」はアーティスト名・曲名を表示させるUI部品
- 「window1」ノードは大きなスクリーンに映す用

![スクリーンショット 2021-12-12 9.36.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/aab1eb96-f19e-9866-2eb0-66c29c869a7a.png)
「container_L」ノード内に入ってみましょう。

##### 「container_L」ノード内
ここでは、映し出す５つの映像を加工・出力しています。

- 左隅のノードに元となる映像を読み込む（mp4やmovをノードにドラッグアンドドロップで取り込めます）
- 映像を格納しているノードをオペレーター（映像を加工できるノード）に繋いでいく
- 加工後のノードをnullノードにつなぐ（nullに繋げるという操作は慣例として必要だそうです）
- 加工後の映像ノードを「switch1p」（switchノード）に集約させる
- 「buttonContainer」内で各映像にボタン機能をもたせて「switch1p」（switchノード）に紐付ける

![スクリーンショット 2021-12-12 10.07.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/b666ac6b-acd4-13b4-fc35-d484686b0f92.png)
「buttonContainer」ノード内に入ってみましょう。

##### 「buttonContainer」ノード内は以下の構成です
ここでは、各映像にボタン機能を持たせています。

- 「button1p〜」に「container_L」の各映像のノードを紐付ける
- CHOPオペレーターを使って各ノードのIndex値を制御する（詳細は後述）
- 「out1」ノードを、先述した「switch1p」（switchノード）に紐付ける
- ボタンが押されたら python 内でIndex値をキャッチ
    - 先述した「project1」ノード内の「textArtist_L」「textSongTitle_L」に文字列を流す

![スクリーンショット 2021-12-12 10.11.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/8e5f3064-d40c-a720-d93b-3537c50d616d.png)


#### 制作過程の一部
映像を加工
↓
ボタンと映像を紐付け
↓
映像押下時にテキストを表示

#### 映像を加工
今回は、２つの映像を重ねて１つの映像として出力ます
![スクリーンショット 2021-12-11 19.02.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/50144179-17a4-ecc9-89d8-e6a4aca914e9.png)

以下の２つの映像を使います
１つ目の映像
![スクリーンショット 2021-12-11 20.38.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/e695337d-53e5-031a-d02a-63bc4393d763.png)

2つ目の映像
![スクリーンショット 2021-12-11 20.40.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/fe7286d8-707e-548e-a338-fb6ba4695f5c.png)

１つ目の映像はクロマキーで一部を透明してみます
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/63c969af-502d-4d67-f7c1-14bf81448a32.png" width="400" /><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/790f5b79-38c3-16f3-adcb-2df4b711e110.png" width="400" />

そして add オペレーターを用いて２つの映像を繋ぎます
![スクリーンショット 2021-12-13 11.07.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/37ae4376-7396-c26c-1c9a-458373f6345a.png)

すると以下のように簡単に加工することができました
![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/9e1e0d8a-3ebc-a1ec-9a8f-a05d6f09636c.gif)

加工を行うオペレーター
「見た目」の加工が行える`TOP`オペレーターは、映像をミラーにして回転させたり、ブラーで映像をぼやかしたり、映像の輪郭だけ切り出したりと、多岐にわたります。
![スクリーンショット 2021-12-11 20.46.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/0ef0671b-1f99-cebf-a3ca-b66cb0c13a2f.png)

#### ボタンと映像を紐付け
各ボタンに映像を追加して映像を切り替えられるようにします。
![スクリーンショット 2021-12-11 21.34.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/619a6c73-edfe-6a83-0cc5-7413d913a315.png)

- 利用したオペレーター（以下記述順に繋いでいく）
  - [COMP]button： ボタン
  - [CHOP]math： Index値を管理
      - Index値： 各映像から送られてくる値。５映像あるので値は「1〜5」となる
  - [CHOP]override: 送られてきた最新のIndex値を保持します
  - [CHOP]limit: Index値の上限を記憶する（今回は5映像なので5に設定）
  - [CHOP]out: 映像出力するために out に繋ぎます
  - [CHOP]null: 慣例として最後は null を繋ぐのがいいそうです

上記で使用したオCHOPは「値」の生成や制御を行うオペレーターとして利用します。
![スクリーンショット 2021-12-11 21.58.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/68f4e11a-ab00-4681-2dc4-9f7bc0458493.png)

#### 映像押下時にテキストを表示
ここまで 「映像を加工」 → 「ボタンと映像を紐付け」 を大雑把に説明しました。
**他にも必要な処理がたくさんありますが、今回は割愛してポイントとなる箇所だけの説明にしました**
ここから、映像をクリックしたらアーティスト名・曲名を python で出力させたいと思います。
![スクリーンショット 2021-12-11 22.10.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/ea295c30-e881-9ddb-1008-94ee8d2295d4.png)

python を利用する場合は、[DAT]CHOP Execute というオペレーターを利用します。
![スクリーンショット 2021-12-11 22.30.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/9a822ea2-2251-c6b4-12cd-c3504ed0fd40.png)

配置すると以下のようなテンプレが出てきます
![スクリーンショット 2021-12-11 22.32.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/2737af95-ff08-48e1-fccd-eef36a5038a3.png)

CHOP Execute では、先述した 「ボタンと映像を紐付け」 にて押下されたボタンのIndex値を受け取ることができます。

```CHOPExecuteの各メソッドが呼ばれるタイミング.
onOffToOn: 流れてきた値が 1 以上の場合
whileOn:　流れてきた値が 0 を超えている
onOnToOff: 現在 0 以上だったが流れてきた値が 0 以下になった
whileOff: 流れてきた値が 0 以下
onValueChange: 値が変化した場合
```

今回は、5つの映像＋真っ黒な映像（Index == 0.0）も含めるため、`onValueChange`を利用します。
受け取った Index値（映像） によって条件分岐させてアーティスト名・曲名を流します。

```/private/tmp/touchtmp/dat_chopexec2__td_79904_50.py

# me - this DAT
# 
# channel - the Channel object which has changed
# sampleIndex - the index of the changed sample
# val - the numeric value of the changed sample
# prev - the previous sample value
# 
# Make sure the corresponding toggle is enabled in the CHOP Execute DAT.

import random

def onOffToOn(channel, sampleIndex, val, prev):
	return

def whileOn(channel, sampleIndex, val, prev):
	return

def onOnToOff(channel, sampleIndex, val, prev):
	return

def whileOff(channel, sampleIndex, val, prev):
	return

def onValueChange(channel, sampleIndex, val, prev):
	# テキストノードオブジェクト
	textArtistL = op('/project1/textArtist_L')
	textSongTitleL = op('/project1/textSongTitle_L')
	# 各映像のIndex値によって分岐
	if channel == 0.0:
		textArtistL.par.text = ""
		textSongTitleL.par.text = ""
	elif channel == 1.0:
		textArtistL.par.text = "Avicii"
		textSongTitleL.par.text = "Levels"
	elif channel == 2.0:
		textArtistL.par.text = "Zedd"
		textSongTitleL.par.text = "Stay the Night"
	elif channel == 3.0:
		textArtistL.par.text = "Tiesto"
		textSongTitleL.par.text = "Fat Beat"
	elif channel == 4.0:
		textArtistL.par.text = "Marshmello"
		textSongTitleL.par.text = "Light It Up"
	elif channel == 5.0:
		textArtistL.par.text = "Sam Feldt"
		textSongTitleL.par.text = "The Riddle"
	return
```
##### 処理について
`textArtistL = op('/project1/textArtist_L')`
テキストノードのパラメータにアクセスしたいため、op('/project1/textArtist_L')としてパスを指定してオブジェクトを作成します。

`textArtistL.par.text = "Avicii"`
textプロパティに値をセットします。

<a href="https://docs.derivative.ca/OP_Class" target="_blank" rel="noopener noreferrer">op</a>: TouchDesignerの大元クラス「OP Class」
<a href="https://docs.derivative.ca/Par_Class" target="_blank" rel="noopener noreferrer">par</a>: 各種パラメータを保持しているクラス「Par Class」
（ドキュメント上、textがどこに属しているのか分かりませんでした）

##### VSCodeなどの外部エディタ使う
TouchDesigner は外部エディタを利用してコーディングすることができます。

1.Preferences からエディタを指定します
![スクリーンショット 2021-12-11 23.21.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/8d1cc1ee-9499-465f-8de3-8d5bda6ac469.png)
2.chopexec上で右クリック 「Edit Contents...」を選択すると指定のエディタが起動します
![スクリーンショット 2021-12-11 23.24.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403173/d21f870f-2094-aefd-22ed-eaff957d6a64.png)

### 用途
作成したVJコントローラーは、メインウィンドウを大きいディスプレイに映して、VJコントローラーは手元で操作するといったイメージです。
<iframe width="560" height="315" src="https://www.youtube.com/embed/OSpQFIWumzQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## まとめ
#### 苦労した点
- python を TouchDesigner で利用すると何が嬉しいのか・どう活用していけばいいか苦しみました（もっと複雑な構成の時に本領を発揮するのだと思います）
- 期待した動作にならない
  - 指定した映像に切り替わらない、流れてくる値がおかしかったのでノードを一つ一つ見直しました

#### 良い点
- 直感的にプログラムできる
- 加工が比較的に簡単に行える
- CHOPオペレーターやデータの流れ、ユーザーの入力、入力に対する処理、そして出力の考え方はiOS開発に通ずるものがあった
- オペレーターだけでは手の届かない痒い所は ptyhon で補える
- そして、python の elif は洒落てると思いました

#### 気になる点
- TouchDesignerはバージョン管理(git等）はできなくはなさそうですが、なかなか難しいようです（あったとしてもコンフリクトで頭が痛くなる気がしてます…どういう方法で共同作業しているのだろう…）

TouchDesignerはプログラム・ムービー・グラフィック・音を扱えるので、プログラミングとデザインの中間に位置しているような印象で、モノづくりを始めるとデザイナーにはデザイナーの領域、エンジニアにはエンジニアの領域で環境が分かれていますが、***TouchDesignerはエンジニア・デザイナーとの境界がなく***、お互いの領域を見て学べられる環境だと感じました。
また、スマホやカメラとも連携できるなど外部連携も豊富らしく、直感的に作れる事ができて面白いです。

## 参考・使用した素材・書籍
基礎を教えてくださった動画です（感謝）
TouchDesignerでの初めては :banana: 回しでした
【 TouchDesigner入門講座 】とりあえずバナナまわしてみよう #00
https://youtu.be/1gxN1-dUfyw

グラフィックデザイナーBeeple氏が提供しているフリーの映像（VJ loops）集
https://www.beeple-crap.com/vjloops

TouchDesigner（カナダのDerivative社）
https://derivative.ca/

python In TouchDesigner
https://docs.derivative.ca/Python_Classes_and_Modules

アート展でもTouchDesignerが利用されているそうです
https://youtu.be/GpRksCaf2SI