【TouchDesigner】DJシステムをつくる

この記事は[TouchDesigner Advent Calendar 2021](https://qiita.com/advent-calendar/2021/touchdesigner)の20日目の記事です。

#はじめに
これはTouchDesignerでDJのシステムをつくろうとしたけど障壁が多すぎたという話です。
当初の目的は果たせなかったものの、Pythonのことをよく知れたし紹介できそうなので良し。

↓一応今回作った画面はこんな感じになります。（クロスフェーダーが無い・・・）
![スクリーンショット 2021-12-19 150108.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/8ce0eb00-a4e1-1662-2e89-91532ebe832f.png)

下記に今回のファイルアップしてます。
Pythonの必要なモジュールはrequiments.txtを使用してインストールしてください。（venv推奨）

https://github.com/atsonic/DJSystem

#お品書き
以下DJシステムに必要な最低限の機能です。

1. 音声ファイルリスト取得/表示
2. 音声ファイルの波形表示（Python＋Script TOP＋.venv）
3. 音声ファイルのBPM計測（Python＋subprocess）
1. BPMをシンクロ
2. 頭出し～Cue
3. イコライザー
4. ミックス～音声出力（オーディオインターフェースでの出力）



###1. 音声ファイルリスト取得/表示
これはスニペットと通常のオペレータを使えば簡単にできます。

- feildFolderBrowser・・・フォルダ選択のダイアログを出せるスニペットです。
- Folder Dat・・・feildFolderBrowserが選択したフォルダパスを参照させると、そのフォルダにあるファイルとパスをテーブルで出力してくれます。
- radioList・・・Folder DatのOutを繋ぐと、そのファイルリストをラジオボタンのリストにしてくれて、ボタンを押すと該当のセルをOutしてくれます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/f2e95c6a-fbb3-d7fe-0b65-4c0ab654f33a.png" width="50%">
これで音源の選択ができるようになりました。

###2. 音声ファイルの波形表示（Python＋Script TOP＋.venv）
これが鬼門でした。
**File in CHOP**で音声ファイルを読み込むと波形を表示してくれるので**OP Viewer COMP**に入れれば良いかと思ったのですが、**ファイルを読み込むと一瞬タイムラインが止まります。**
この時点で再生している音も止まってしまうのでシステムとして破綻してる。
なのでしょうがなくPythonで実装を試みました。

####2-1. Script TOP
波形の生成は**Script TOP**で行います。Script TOPは、Pythonを記述するとTOPにイメージを出力してくれる便利なTOPです。

が、音源選択から波形の生成を1つのToeファイル内で波形の生成を行うとやはり全体の処理が止まってしまったため下図の様に**2つのToeファイル**で行いました。
![スクリーンショット 2021-12-09 211343.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/2fbb6ffc-a375-3bcc-890c-33b786890cde.png)
こうすることでメインのToeファイルの処理を止めることなく波形の生成が行えます。
Python自体は[**torchaudio**](https://pytorch.org/audio/stable/index.html)というライブラリを使用しています。

↓これは「1. 音声ファイルリスト取得/表示（Operator）」から波形生成までのスクショです。
![スクリーンショット 2021-12-09 200153.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/21547bab-e11a-f4a4-3d3b-8a9292431f17.png)

####2-2. 仮想環境（venv）について
今回波形生成のPythonをTouchDesigner内で実行するにあたり**仮想環境（venv）**を使いました。
Anaconda環境含め、PCには複数Pythonがインストールされていますが、venvを使うと本プロジェクトのみに適したPython環境を構築することができます。

参考・・・[Extending TouchDesigner @MUTEK.JP](http://satoruhiga.com/post/extending-touchdesigner/)

1点、Pythonモジュールの探索パスを追加する際に**append**ではなく**insert**を使用しました。
appendは探索パスリストの最後にvenv環境のパスを追加しますが、insertは第一引数に0を指定することでリストの最初に追加することができます。

今回NumPyを使用していますが、appendで追加した場合別環境のNumPyを参照してしまってバージョンエラーが出たので、venv環境のモジュールを最初に認識させるべくこの対策を取っています。

```python
import sys, os
path = os.path.join(os.path.abspath('.'), '.venv\Lib\site-packages')
if not path in sys.path:
	sys.path.insert(0, path)#←ここをappendじゃなくてinsertにしてる
```

####補足
正直ここでTouchDesignerでのDJシステムの完成を断念して各機能の開発にしました。
大きな理由は下記2点に尽きます。

1. Pythonでmp3を使うことが難しい
2. 波形を自由に伸び縮みさせることが難しい

私の所有するデジタル音楽データはmp3が多いのですが、それが使えないとなると作る意味合いが無くなります。
また、曲の頭出しなどは波形の細かい部分を見てキューをセットするのですが、波形が自由に変形できないとなるとそれも難しい。
残念。。
ですが、単機能ごとの開発は続けます。

###3.音声ファイルのBPM計測（Python＋subprocess）
TouchDesignerでは[リアルタイムでBPMを計測するチュートリアル](https://www.youtube.com/watch?v=t42tVSw6cXA)も公開されていますが、ファイルを読み込んだ時点でBPMが知りたいので、これもPythonを使用します。

これにはPythonの[**librosa**](https://librosa.org/doc/latest/index.html)というライブラリを使いました。
ですがTouchDesigner内で実行するとTouchDesignerが固まって落ちてしまったので、これは**subprocess**を用いて**TouchDesignerの外部**でPythonを実行します。

subprocessはTouchDesignerのパフォーマンスを下げることなくPythonの複雑な計算などを行えるためおススメです。

参考・・・[TOUCHDESIGNER | PYTHON AND THE SUBPROCESS MODULE](https://matthewragan.com/2019/08/14/touchdesigner-python-and-the-subprocess-module/)

具体的にはsubprocessで外部のプログラムを呼び出してcallbackを受け取るという感じですが、callbackというかOSCで計算結果を戻してもらっています。

![スクリーンショット 2021-12-09 211359.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/e29b7014-552b-4010-b1e9-a21f388fdcac.png)

この時、普通にやると**プロンプトのウィンドウが立ち上がってしまいます**。
これは最高にいけてないので、**ウィンドウを起動せずサイレント実行をする記述**をします。

```python
port = str(op('udpin').par.port.val)

#venv環境のpythonを実行するようにpython.exeを指定
cmd_python = '{}/.venv/Scripts/python.exe'.format(project.folder)
#実行するpythonファイルを指定
cmd_python_script = '{}/scripts/detectBpm.py'.format(project.folder)
python_args = [cmd_python, cmd_python_script]
#OSCに使うポート番号と、BPMを計測するためのファイルパスを引数で渡す指定
script_args = ['-p', port, '-f', filepath]
cmd_args = python_args + script_args
op('udpin').par.clear.pulse()

#ここでプロンプトのウィンドウを非表示にする設定
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
#コマンド実行
subprocess.Popen(cmd_args, startupinfo=si)
```
↓Select DATの音源ファイルパスが変わるとpythonが実行され、結果がUDP INに返ってくるところです。
![スクリーンショット 2021-12-14 114349.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/5aed7f9b-d9a4-8a2c-145d-fe4f9f44a1cd.png)

一方で音声ファイル、特にダンス系のDJを目的としたファイルには、メタ情報にBPMの情報がついているものがあるのでその情報を使うのもありです。

###4. BPMをシンクロ
音源のBPMが計測出来たところで2つの音源のBPMをシンクロさせる機能を検討します。
まずBPMの考え方ですが、TouchDesignerにおいては**Audio File In のSpeedパラメーター**に以下の計算式で適用させようと思います。

**セットしたいBPM / 自身の今現在のBPM = Speedの倍率**

Audio File Inで音源を読み込んだ時、**Speedは1**になっています。
セットしたいBPMを、自身の元々のBPMで割ればSpeedの倍率が計算できることになり、その値をSpeedに読み込ませてあげることで、片方の音源からもう片方の音源へBPMのシンクロができます。
これはMath CHOPで2つのチャンネルをDivideしてあげれば簡単に計算ができます。

![スクリーンショット 2021-12-19 151048.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/c35a6e44-87c6-2069-2f72-6a59c2cf2eff.png)

###5. 頭出し～Cue
音源のCueをセットする際に音源をシークします。
これは**Slider COMPと音源の長さを一致させる**ことで、Slider COMPのバーを動かして音源の特定の位置で頭出しをし、Cueにセットさせることがきます。

その際Audio File Inの**Play ModeをSpicify Index**にし、**Indexパラメーターを0~100の値で指定**することで実現します。
Slider COMPは0~1の値を出力するのでMath CHOPで100倍してその値をIndexに読ませます。
![スクリーンショット 2021-12-19 151531.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/9570ce6f-e45c-d0ad-d61e-16f82de15e5d.png)
Slider COMPだとあまり小回りが利かないので、ターンテーブル系の、微調整が可能なUIにするとより良いと思います。これをうまくやれば**スクラッチもいけます**。

###6. イコライザー
イコライザーはAudio Band EQの、High / Middle/ Lowそれぞれの周波数をknobの出力でコントロールします。
今回knob含めGUIはPaletteのものを使ってます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/13971371-d723-2c0f-65a7-6d2363773f5a.png" width="50%">
**Audio File InからAudio Band EQにつなぎ、Audio Device Ou**tすれば調整した音が出力可能です。

###7. ミックス～音声出力（オーディオインターフェースでの出力）
ミックスと書いておきながらクロスフェーダー実装してないんですが、、最後にオーディオインターフェースの話をします。
オーディオインターフェースとは、マルチチャンネルの音声を出力できるデバイスで、PCでDJをする場合は必須のものです。私は10年以上前に購入した**Audio 4 DJ**というデバイスを使用しています。これは2チャンネル（ステレオで4チャンネル）の出力に対応しています。
![news_photo1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/d624fd95-53eb-94a6-0494-a6bd758b29ec.jpeg)

TouchDesignerはオーディオインターフェースにも対応しておりマルチチャンネルに出力ができます。
ドライバがインストールされていればおそらくTouchDesignerでも認識ができると思います。
**Audio Device OutのDeviceパラメーター**に、どのチャンネルで出力するかの選択ができるようになります。
![スクリーンショット 2021-12-19 153338.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/a2f2d4ac-002f-e826-4f05-782221c137cf.png)

最初**DriverをASIOにしてやってみたんですがうまくいかず、**defaultにしていますが、ASIOだと低レイテンシーなので、そちらの方が良いらしいです。

で、Audio 4 DJの場合、**チャンネルAをMaster Out（スピーカー）、チャンネルBをモニター（ヘッドフォン）**に指定することで、曲を流しながら頭出しなんかができます。

#最後に
最初はもっと簡単にできそうかなと思いましたが、最初の方で結構心を折られました。
GitHubにアップしたソースではある程度機能するようにGUIを組んでいます。
バインドの仕方とか細かいところで記事にできそうなところはありましたが、今回は割愛しています。

この先これをアップデートするかどうか迷いますが、、
これを読んでいただいた人の何かの一助になれば幸いです。