TouchDesignerでAPC40 MK2のLEDを操る

[TouchDesigner Advent Calender 2024](https://qiita.com/advent-calendar/2024/touchdesigner) の1日目の記事です！

[Akai Pro APC40 MK2](https://www.akaipro.com/apc40-mkii.html)（以下、単に **APC40** と呼びます）というMIDIコントローラーのLEDを、TouchDesignerから光らせてみて、MIDIコントローラーのLED制御について学ぶ記事です。

今回扱うライブラリやサンプルは、以下のGitHubリポジトリにて公開しています。

https://github.com/monoton-music/td-apc40mk2

# 記事の目的
- APC40のLED制御を例として、TouchDesignerからMIDIコントローラーのLEDを操る方法を学ぶ
- TouchDesignerにおける、Pythonを使った自作モジュールの簡単な使い方について学ぶ

# TouchDesignerでMIDIコントローラーのLEDを操る

## LEDを制御することのメリット

MIDIコントローラーは、ボタンやパッド、ノブ、スライダーといった操作子の情報をMIDIイベントとして送信します。
加えて、LEDが付いているものでは、MIDIイベントを受信することでLEDの状態を更新できる場合が多いです。

この場合、コントローラーはユーザーからPCへ情報を伝えることにも使えるし、逆にPCからLEDを制御することで、情報をユーザーに伝えることもできます。

Ableton Liveなど特定のソフト向けに、LEDがソフトに反応して変化するプリセットなどを備えていることがありますが、このような仕組みを自分でも用意することができます。

TouchDesignerで映像送出、照明制御、VJなど、現場（たいてい暗い！）で操作するようなシステムを組んでいる場合、LEDの表示も適切に設定することによって、操作をわかりやすくし、ミスを減らすことに繋がります。

筆者は、TouchDesignerで組んだシステムを使ってVJを行っています。
以下のようにAPC40のLEDを活用しています。

![IMG_6374.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3706619/18764268-1c49-ba79-f0e0-db969f3525a3.jpeg "TouchDesignerでLEDを制御しているAPC40")

一見してコントローラー起動時のレイアウトと変わらないように見えますが、VJシステムの各チャンネルの状態やテンポを反映していたり、映像のhueをコントロールするノブの下には処理結果の色が表示されたりと、双方向の制御を組んで活用しています。

[hueコントロールのデモ動画 – YouTube](https://youtube.com/shorts/CoxGLsNNrZM)

## LEDを制御する上で面倒なところ

多くのコントローラーでは、コントローラー上のそれぞれのLEDについて、対応するMIDIチャンネル、noteまたはCCのインデックスが割り当てられています。
定められたnoteまたはCCを、MIDIイベントとしてコントローラーに送信することで、LEDを操ることができます。
色も指定できる場合は、noteのvelocityで指定することが多いです。

APC40では、以下のプロトコルにその制御が定められています。
[Akai APC40 Mk2 Communication Protocol – Akai Pro](https://cdn.inmusicbrands.com/akai/attachments/apc40II/APC40Mk2_Communications_Protocol_v1.2.pdf)
ここでいうプロトコルとは、APC40が送信するMIDIイベントの形式や、受信した際のそれぞれの値に対する振る舞いを定めたものです。

例えば、APC40で40個並んでいる長方形のパッド（Clip Launch）の左上を赤色に光らせるなら、形式的には
```json
{
  "channel": 1,
  "note_number": "0x20",
  "velocity": 5
}
```
のように情報を準備して、これをMIDI noteとしてAPC40に送信することで実現できます。（ **例1** ）

これは直感的ではありません。
コントローラー上のLEDとMIDIチャンネル、note、CCとの対応を直接把握するのは難しいので、プロトコルを何度も確認することになります。
また色についても、velocityと対応する色との間ではっきりした規則がなく、プロトコルのリストから欲しい色を探すのも一苦労です。

APC40のようにグリッド状にパッドが並んでいるのであれば、Clip Launchの何行目何列目のLEDを何色に光らせる、といった具合で指定できたら扱いが楽になります。

つまり、形式的には
```json
{
  "type": "Clip Launch",
  "row": 1,
  "column": 1,
  "color": "red"
}
```
のように指定できたら直感的です。（ **例2** ）

# MIDI Out CHOPをPythonから制御する

[MIDI Out CHOP](https://derivative.ca/UserGuide/MIDI_Out_CHOP) と [midioutCHOP Class](https://derivative.ca/UserGuide/MidioutCHOP_Class) を使えば、DATなどで用意したPythonのスクリプトを使ってMIDIイベントを送信することができます。

先ほどの **例1** に対応するコマンドであれば、
```py
# MIDIイベントを送信するオペレーターを指定
midiout = op('midiout1')

# 点灯または点滅の種類を指定
channel = 1 
# 制御するLEDの位置を指定
note = 0x20 
# LEDの色を指定
velocity = 5 

#　MIDIイベントを送信（送信の仕様上、少し値に調整を加えています）
midiout.sendNoteOn(channel, note + 1, velocity / 127) 

# Clip Launchの左上が赤色に点灯する
```
のようになります。

# 自作のモジュールでMIDIイベントの扱いを楽にする

元々あるこのクラスに加えて、本来は複雑なMIDIイベントの割り当てを、直感的に扱えるように変換してくれる自作のモジュールを用意します。
これらを使って、シンプルなコマンドによってLEDを操ることを試みます。

自作したクラスの集まりをモジュールにしたものが、サンプルリポジトリの`/src/apc40mk2.py`です。

これを使えば、Clip Launchの左上を赤色に光らせるには、先ほどの **例2** のように、
```py
# MIDIイベントを送信するオペレーターやデバイスのモードを指定
import apc40mk2
apc = apc40mk2.APC40MK2(op('midiout1'))
apc.mode.set_device_mode(mode=2)
# ここまで、個別LEDの色に直接関係ないので、あまり気にしなくても大丈夫です

# 制御するLEDの行を指定
row = 1 
# 制御するLEDの列を指定
column = 1
# LEDの色を指定, 他にも`#ff0000`のような指定も可
color = "red" 
# 点灯または点滅の種類を指定
led_type = 0 

#　MIDIイベントを送信
apc.led.set_clip_launch(row, column, color, led_type) 

# Clip Launchの左上が赤色に点灯する
```
と書くことができます。

MIDIコントローラーの配置の様子に近い指定方法となっており、先ほどより見通しが良くなったかと思います。

## 自作モジュールのインポート方法

TouchDesignerでは、Pythonが実行されるDATと同じ階層に置かれているDATを、
```py
import datName
```
の形でモジュールとしてインポートすることができます。
参考：[MOD Class – Derivative](https://docs.derivative.ca/MOD_Class)

ここでは、`apc40mk2.py`をネットワークエディターにドラック＆ドロップすると作られる`apc40mk2`というText DATに対して、同じ階層にあるDATからそれをモジュールとして読み込むようになっています。

一度読み込んでしまえば、あとは自分で用意したクラスでも使うことができます。

今回の場合は、まず
```py
apc = apc40mk2.APC40MK2(op('midiout1'))
```
として、親クラスに引数の形で、MIDIイベント送信に使うMIDI Out CHOPを指定すると、各種コマンドを使えるようになります。


## LEDの色指定についての工夫例

色の指定を行うvelocityについて、プロトコルに定められたような`5`などのインデックスそのままではもちろん、`'#FF0000'`のようなカラーコードや、`"red"`のような色の一般名でも指定できるようになっています。

カラーコードについては、実際にはAPC40が出せるLEDの色の種類は高々128種類なので、指定されたカラーコードに最も近いインデックスを探してくれるようになっています。

わざわざプロトコルのカラーコード表と対峙しなくても、なんとなくこの辺りの色が出したい、という感じで指定できます。

具体的な実装としては、与えられたカラーコードと、実際に出せる色としてプロトコルに記載のあるカラーコードとのうち、ユークリッド距離（RGBの値それぞれの差の2乗和）が最も近いものを採用するだけの簡単なアルゴリズムになっています。

色の一般名については、
```py
self.color_map = {
    "black": 0, "dark gray": 1, "gray": 2, "white": 3, "red": 5, 
    "orange": 9, "yellow": 13, "green": 21, "cyan": 37, "blue": 45, 
    "purple": 49, "magenta": 53, "pink": 57
}
```
の13種類を指定できるようになっており、より簡易な運用ではこちらでも十分かと思います。

このような工夫が、自分でモジュールを用意するところの楽しく便利なところだと思います。

## `apc40mk2.py`の補足

`apc40mk2.py`は、`APC40MK2`という親のクラスと、パッドやボタンのLEDの制御を行う`LEDController`、ノブの値と周りのリングLEDを制御する`KnobController`、デバイスの動作モードを設定する`DeviceModeController`の計3つの子のクラスに分かれています。

関数それぞれの細かい使い方については、モジュールに書いてあるDocStringを参照していただければと思います。

その際、APC40のプロトコルと見比べてみると、どのような処理をしているか分かりやすいかもしれません。

先ほどの`led.set_clip_launch(row, column, color, led_type)`では、以下のようにしてMIDI noteのインデックスを指定しています。なお、一部簡略化して示しています。
```py
def set_clip_launch(self, row, column, color, led_type):
    self.midiout.sendNoteOn(led_type + 1, (5 - row) * 8 + column, color / 127)
```

# 応用
以上のような手法は、APC40以外のMIDIコントローラーであっても応用することができます。
使いたいコントローラーのMIDIプロトコルを見ながら、自分の使いやすい形でMIDIイベントを送信できるようなモジュールを用意し、実際の呼び出しはそのモジュールを通して行う、という流れは変わりません。

これだけで長くなるのでまた別の機会にと思いますが、他には、8x8のRGBカラーのLEDが配置されたNovationのパッド型コントローラー、Launchpadシリーズ用のモジュールを用意し、TouchDesigner上のTOPの8x8の映像と同じ色でパッドを光らせる、という応用を試したりもしました。

ぜひお手持ちのMIDIコントローラーでも試してみてください！

# 最後に
この記事について分からない点やご指摘があれば、ぜひ何らかの形で届けてくださると嬉しいです。
コメント、リプライ、DMなどお気軽にどうぞ！
残りの記事を読むのが楽しみです！

### monoton（ものとん）について

音楽制作とVJ、ハードウェアを軸に、空間演出に取り組むテクニカルアーティストです。
楽曲リリース、TouchDesignerベースのVJシステム開発を自主制作として行いながら、リアル・バーチャルの双方でライブイベントや展示型作品を手がけています。

Twitter(X): [@monoton_music](https://x.com/monoton_music)