ATOM MatrixでAtem Mini対応のタリーライトを作ってみた

# きっかけ
私が所属する研究室はIoT関連の研究室のため、M5Stackシリーズがいくつかあるのですが、その中でATOM Matrixが使用されることなく奥底に眠っていて「何か使い道がないかな？」と思ったので今回とあるものを作ってみました。
アドベントカレンダーの記事として勢いで書いたので、こんな使い道もあるんだなぁぐらいの感覚でお読みいただけると幸いです。

# TL；DR
- M5Stackシリーズの1つ、ATOM Matrixを利用してビデオスイッチャーに対応するタリーライトを作ります。
- タリーライトとは(超簡潔に言うと)映像収録の現場で、出演者等にカメラを識別してもらうために利用される赤や緑のランプのこと。
- ビデオスイッチャーには、Blackmagic Design社のAtem Miniを使用し、映像の切り替えに応じてタリー(ATOM Matrix)のLEDの色が切り替わるようにします。

# 用語の説明
この記事をお読みの方の中には、普段から映像機器に触れている方もいれば、たまたま記事にたどり着いて、ビデオスイッチャー？タリーライトって何？となる方もいるかと思いますので、このセクションで簡単にご紹介しつつ進めていきます。
## M5Stack ATOM Matrix
ATOM Matrixは、M5Stack社が開発した小型のIoTプログラマブルデバイスです。5×5のフルカラーLEDマトリックス表示器と内蔵ボタン、ESP32マイコンを搭載し、Arduino IDEやUIFlow等で簡単にプログラミングができます。サイズは24x24x14mmと超小型で、もはや親指サイズ。USB-Cで給電・書き込みが可能です。
この記事の執筆時点では<a href="https://ssci.to/6260">スイッチサイエンス</a>さんにて、1個およそ2,900円ほどで販売されていました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1237655/2a32b859-f1b0-30d6-7c77-9b694b687b01.png" width=40%  />　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1237655/991978d0-4d3a-5e0b-e790-b306f277f005.png" width=40%  />
※画像は公式ドキュメントより引用(https://docs.m5stack.com/en/core/ATOM%20Matrix)


## ビデオスイッチャー
ビデオスイッチャーは、複数の映像入力(カメラなど)から必要な映像を選択し、出力する映像を切替えるための装置です。放送局やイベント会場などで使用され、カメラやビデオプレーヤーなどの映像ソースをリアルタイムで切り替えたり、特殊効果(例: ワイプやテロップ)を加えたりすることができます。今回使用するのは、Blackmagic Design社のAtem Miniであり、4チャンネルのHDMI入力が可能で、手ごろな価格で利用することができる製品です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1237655/0f811415-7dca-fd99-0cdc-3a824dbe99b2.png)

※Atem Miniと同シリーズのAtem Mini Proの画像を公式より引用(https://www.blackmagicdesign.com/jp/products/atemmini)。

## タリーライト
**今回はこちらを制作していきます！**
タリーライトは、映像配信・制作現場で使われる小さなランプで、どのカメラが現在使用中（録画中や配信中）かを示す役割を持っています。通常、赤色に点灯する際は「Program(オンエア中)」を示し、出演者やスタッフがどのカメラを意識すべきか一目で分かります。また、カメラの「Preview(スタンバイ)」状態を示すために緑色等のランプを使うことでスムーズな進行を支える重要な役割を果たします。

# 実装
このセクションではタリーライトの実装について簡単に解説していきます。
## 用意したもの
- Blackmagic Design Atem Mini × 1台
- M5Stack ATOM Matrix × 4台
- Wi-Fiネットワークと、Atem Miniをネットワークに接続するためのLANケーブル等
- ATOM Matrixの書き込み/給電用のType-Cケーブル等
- Arduino IDE v2.2.1

## 使用したライブラリ
今回、タリーライトを実装するにあたって、ATEM Miniを制御するために必要な<a href="https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering">SKAARHOJさんのATEM関連Arduinoライブラリ</a>を使用させていただきました。
今回のプログラム内で、それぞれ以下を使用します。
- ATEM.h
- ATEMbase.h
- ATEMstd.h

また、別途WiFi接続用の、WiFi.hと、FastLED.h(<a href="https://github.com/FastLED/FastLED">FastLED</a>)を追加で使用してます。


## 制作するもの
※下の画像内では、ATOM Matrixはそれぞれ左から順にそれぞれ1~4までのHDMI映像入力に対応するように並べています。

1. それぞれの映像入力のボタンを押すと、Previewする映像を切り替えることができます。それに伴って、対応するATOM Matrixが緑に点灯していることがわかります。
2. スイッチャー右下部のCUTやAUTOボタンを押すことで、赤色(Program中の映像)と緑色(Preview中の映像)を切り替えることが可能です。映像の切り替えに伴って、タリーの色も入れ替わっています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1237655/ff8e36a6-e503-fd18-dc4a-6bc7695c9958.gif" width="100%"/>

## サンプルコードと解説
:::note warn
今回はArduino IDEでMatrixにサクッと書き込めるように、動作に必要な情報(SSIDやIP)などはハードコーディングしています。
実際に運用する際は、このあたりは改善の余地があるかと思います。
:::
```
#include <WiFi.h>
#include <ATEMbase.h>
#include <ATEMstd.h>
#include <FastLED.h>

// WiFi設定
const char* ssid = "ここにSSIDを入力";
const char* password = "ここにWi-Fiパスワードを入力";

ATEMstd atem;

//ここにAtem MiniのIPアドレスを入力 (例) IPAddress atemIP(192, 168, 0, 101)
IPAddress atemIP(XX, XX, XX, XX);

// LED設定
#define NUM_LEDS 25
#define DATA_PIN 27
CRGB leds[NUM_LEDS];

// デバイスのID (1 ~ 4 を設定)
// デバイスIDに紐づけて、Atem Miniの映像入力を割り当てる(id = 1のときHDMI入力1)
const int deviceID = 1; //HDMI 入力1の状態に紐付ける。

void setup() {
  Serial.begin(115200);

  // WiFi接続
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected!");

  // ATEM接続
  atem.begin(atemIP);
  Serial.println("Connecting to ATEM...");
  while (!atem.isConnected()) {
    atem.runLoop();
    delay(100);
  }
  Serial.println("ATEM connected!");

  // LED初期化
  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.clear();
  FastLED.show();
}

void loop() {
  atem.runLoop();

  // このデバイスの入力チャンネルがProgramまたはPreview状態か確認
  bool isProgram = (atem.getProgramInput() == deviceID);
  bool isPreview = (atem.getPreviewInput() == deviceID);

  // タリーの色を設定
  if (isProgram) {
    fill_solid(leds, NUM_LEDS, CRGB::Red);  // プログラムは赤
  } else if (isPreview) {
    fill_solid(leds, NUM_LEDS, CRGB::Green); // プレビューは緑
  } else {
    fill_solid(leds, NUM_LEDS, CRGB::Black); // オフ
  }

  FastLED.show();
  delay(50);
}

```
## 簡単な解説
上のプログラムを解説しているだけなので、読み飛ばしてもらっても構いません。

<details><summary>解説</summary>

```
#include <WiFi.h>
#include <ATEMbase.h>
#include <ATEMstd.h>
#include <FastLED.h>
```
↑前述したライブラリをここで読み込みます。

```
const char* ssid = "ここにSSIDを入力";
const char* password = "ここにWi-Fiパスワードを入力";
```
↑ ATOM MatixをWiFiに接続して制御するため、SSID, Passwordを書き換えてください。ATEM Miniと同じネットワークに配置する必要があります。

```
ATEMstd atem;
IPAddress atemIP(XX, XX, XX, XX);
```
↑ ATEMの情報を取得するためにATEMstdインスタンスを作成します。
また、ATEM Mini本体のIPアドレスをここにカンマ区切りで記述してください。

```
#define NUM_LEDS 25
#define DATA_PIN 27
CRGB leds[NUM_LEDS];
```
↑ 使用するLEDの個数(NUM_LED)、LED制御のためのピン番号を記述します。
CRGB leds[NUM_LEDS]; はLEDの色を管理するための配列だった気がします。

```
const int deviceID = 1; //HDMI 入力1の状態に紐付ける。
```
↑ ATEM MiniのHDMI入力に対応する1~4の値を設定します。
上記のコードの場合だと、HDMI入力1の状態をタリーで表示します。
**ここの番号を書き換えたうえで、それぞれのATOM Matrix(タリー)に書き込みます。**

```
//setup()関数内
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
}
Serial.println("\nWiFi connected!");
```
↑ Wi-Fiに接続し、接続が成功するまで待機します。

```
atem.begin(atemIP);
Serial.println("Connecting to ATEM...");
while (!atem.isConnected()) {
  atem.runLoop();
  delay(100);
}
Serial.println("ATEM connected!");
```
↑ ATEM Miniに接続し、接続が成功する(atem.isConnected()がtrueになる)まで待機します。

```
FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.clear();
FastLED.show();
```
↑ LEDの種類、ピン、配列等を指定しています。その後LEDデータを一旦リセットし、設定を反映します。

```
//void loop()内
  atem.runLoop();

  bool isProgram = (atem.getProgramInput() == deviceID);
  bool isPreview = (atem.getPreviewInput() == deviceID);
```
↑ スイッチャーの状態を更新し、どの映像入力を選択しているのかを取得します。
Program入力、Preview入力の状態をそれぞれ取得し、先ほど設定したdeviceIDと合致していればTrueになります。

```
if (isProgram) {
  fill_solid(leds, NUM_LEDS, CRGB::Red);  // プログラムは状態は赤
} else if (isPreview) {
  fill_solid(leds, NUM_LEDS, CRGB::Green); // プレビュー状態は緑
} else {
  fill_solid(leds, NUM_LEDS, CRGB::Black); // それ以外はオフ
}
FastLED.show();
delay(50); // ループ速度を調整
```
↑
isProgramの状態に応じてLEDの色を変更しています。
Program状態なら赤、Preview状態なら緑に設定します。
どちらでもない場合はLEDをオフ（黒）にしています。

またLEDの設定を反映し、今回はループの実行間隔を50ミリ秒にしました。
</details>

# 感想
- 使用したATOM Matrixの個体の問題もあるかもしれませんが、長時間点灯させた状態で保持していると、LEDのチラつきや切り替え時の遅延が発生するなどのケースがあるので、この点は改善点として挙げられるかなと思います。
- LEDの点灯に伴って若干の発熱があるので、放熱対策は工夫が必要だったりする印象です。
- ATOM Matrixと給電環境が用意できさえすれば、タリーライトとして運用が可能であることがわかったので、安価に用意したい場合やタリーが急遽必要になった際などでは重宝するのではないかと思います。

# 参考
https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering/tree/master/ArduinoLibs

https://qiita.com/kitazaki/items/f37764fbb4d1d98c552d


# 余談
先日、この記事を執筆する前(構想段階)にInterBEE 2024に参加する機会があり、NHKテクノロジーズさんの技術展示ブースにお邪魔したのですが、本記事でもご紹介したM5 ATOM Matrixを用いたタリーライトの展示がありまして、そちらの方では通信方式として920MHz帯を用いていてとてもに勉強になりました。
Wi-Fiを利用したタリーだと、やはり使用環境によっては遮蔽物等の干渉に左右されやすく、通信の安定性や通信距離に影響が出るといった内容をお話しされていました。