【Unity】DMX512データの受信
前回の記事ではUnityからDMXデータを送信する方法を解説しました。今回は逆にUnityでDMXデータを受信する方法を解説します。
使用環境
Unity 2020.3.12f1
Windows10
1. 準備
検証として以下の機材を用意しました。
オス-メスのXLRケーブル
今回はあくまで検証目的ということで、送信側と受信側の1対1で通信を行うため、オス-メスのXLRケーブルを切断してメス側の切断面の被覆を剥いてコンバータと接続しました。
送信、受信共にケーブル付きの場合はオス-オスキャノンコネクタを使って接続すればケーブルの切断加工をせずに済むかもしれません。この辺りは使用する環境に合わせていただいて構いません。
Q Light Controller+ (QLC+)
PCからDMX信号を送信できるツールです。Win/Mac/Linux版が用意されています。事前にインストールしておいてください。物理的なDMXコントローラーがある場合は不要です。
2. コード
以下のコードを適当なオブジェクトにアタッチします。DMX信号の受信は送信のときと同じようにSerialPortクラスを使用します。ポート番号は実際に使用するポート番号を指定してください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System;
using UniRx;
public class DmxController : MonoBehaviour {
private const long FLAME_BETWEEN_TIME = 100000;// 10,000.0 msec
private static Subject<List<byte>> m_DmxSubject = new Subject<List<byte>>();
private SerialPort m_Serial;
private bool m_IsLoop = true;
private long m_PreTime;
public static IObservable<List<byte>> ReadObservable => m_DmxSubject;
void Start() {
m_Serial = new SerialPort("COM4", 250000);
m_Serial.DataBits = 8;
m_Serial.Parity = Parity.None;
m_Serial.StopBits = StopBits.Two;
m_Serial.Open();
Observable.Create<List<byte>>(Read)
.SubscribeOn(Scheduler.ThreadPool)
.Subscribe(frame => {
m_DmxSubject.OnNext(new List<byte>(frame));
}).AddTo(this);
}
private IDisposable Read(IObserver<List<byte>> observer) {
var frame = new List<byte>();
while (m_IsLoop) {
var data = (byte) m_Serial.ReadByte();
var time = DateTime.Now.Ticks;
if (time - m_PreTime > FLAME_BETWEEN_TIME || frame.Count >= 513) {
observer.OnNext(frame);
frame.Clear();
}
frame.Add(data);
m_PreTime = time;
}
return Disposable.Empty;
}
private void OnDestroy() {
m_IsLoop = false;
m_Serial?.Close();
m_Serial?.Dispose();
}
}
UnityでSerialPortクラスを使用するには、Project Settingsから「Api Compatibility Level」を.NET 4.xに変更します。SerialPortクラスがうまく読み込めない場合は、Unityを再起動してみてください。
また受信関連の非同期処理とリアクティブ処理としてはUniRxを使っています。UniRxはUPM PackageでインストールするとAssetsフォルダではなくPackagesフォルダに配置されるので個人的におすすめです。
作成したこのDmxControllerは1Byteずつ受信したデータを最大513要素のListを1フレームとしてストリームに流しています。これによって使用する側はList要素のindexが1ch分のデータに相当することで扱いやすくしています。
「DMXは最大512chなのでListの要素も512個では?」と思うかもしれませんが、DMXのフレームには先頭に1Byte分のスタートコードが付加されるため全体として513Byteが1フレームになります。
最後にDMXデータを使いたい箇所で以下のコードを記述します。
using UniRx;
[SerializeField] private int m_channel;
void Start() {
DmxController.ReadObservable
.ObserveOn(Scheduler.MainThread)
.Subscribe(frame => {
if (frame.Count > m_channel) {
Debug.Log(frame[m_channel]);// 1ch Data
}
}).AddTo(this);
}
3. 実行
まずQ Light Controller+(QLC+)を起動して「入力/出力設定」のタブを選択してマッピングからDMX信号を送るデバイスを選択します。その後「シンプル卓」タブを選択します。
次にUnityエディタのコードをアタッチしたインスペクタ上で使用するチャンネルを指定し、実行します。
準備が整ったらQLC+のシンプル卓から指定のチャンネルのスライダーを動かしてUnity上に反映されているか確認してみてください。(上記のコード例ではConsole Log)
以下は実際に簡単なステージにUnityちゃんを配置してLightオブジェクトを制御してみた例です。© UTJ/UCL
4. 補足&うまく動かないとき
1つ重要な点として、上記のサンプルコードでは1フレームを区切るための判別方法として1Byteずつ受信した時間を記録し、前回受信した時刻よりも10ms以上間隔が空いた場合に1フレーム分受信したとして処理しています。
DMX信号は一般的に1フレーム30Hz (≒33.3..ms) 前後のインターバルで送信されています。以下の画像はオシロスコープで実際にDMXの信号ラインを観測した波形です。(QLC+を使用)
横軸の1マスが10msです。フレームの先頭同士の間隔は予想通り約33msです。次にフレームの一番最後のデータと次のフレームの一番最初のデータとの時間差を見てみると約10msです。
本来であればこの10ms以下(>数ms)を区切り時間とすれば良さそうですが、実際にSerialPortクラスで受信時間を計測してみると、フレーム区切りとなる最後のデータと次のフレームの時間差はなぜか約32msでした。
おそらくSerialPortクラスのバッファが関連していると思われますが、今回はSerialPortクラスで得られる区切りの約32msの1/3の10msをフレーム区切りとして設定しています。
もし環境によってうまく動作しない場合はコード上のFLAME_BETWEEN_TIMEの値を調整してみてください。単位はDateTime.Now.Ticksを使用しているので、1Tick=100nsです。
5. おわりに
DMXの使用例としては主にムービングヘッドや舞台照明にDMXコントローラーやPCから信号を送って制御することが一般的であるため、逆にUnityにDMX信号を取り込む例はやや特殊であるといえます。
Unityでリアルタイムに手動制御したい場合はDMX照明卓よりもMIDIコントローラーを使用するか、Art-NetでのEthernet経由でDMX信号を取り込む方が現実的かもしれません。
しかしながら、昨今ではバーチャルライブやARライブ等の現実と仮想を連動させたリアルタイム空間制御を行う機会も増えてきたため、今回の記事が少しでも参考になればと思っています。🌱
6. 参考
DMXの仕様については以下のサイトを参考にさせていただきました。ありがとうございます。
この記事が気に入ったらサポートをしてみませんか?