ここまで
前回QLabのOSC通信を利用した表示ツールを作ろうとしたなかで、/listenと/updateの仕様について知見を得ました。
/updateのほうがどうやら表示ツールとしては都合が良さそうだ、ということが開発を進めるなかで分かったのですが、今回は/listenを使って開発していた初期のtouchOSC側での実装と、実際に触ってみて分かった設計上の悩みについて書いていきます。
とりあえずつくってみた
ボタンが4つとそれのラベル、そして取得データ表示用のラベルが1つです。
touchOSCの詳細は省きますが、はじめボタンのTriggerのANY/RISE/FALLに気づかず、二重送信されてしまい苦労しました。
(デフォルトのANYはpressとreleaseのどちらでも走る)
そして、表示用のラベルは、/reply/cue/selcted/durationを受けて、そのArgumentsがtextの中身になるように設定しました。

データは取得できましたが、表示されるのはJSONデータであって、これでは意味をなしていません。
Lua script
ここで、scriptの登場です。touchOSCではLuaスクリプトを埋めこむことができます。
いきなりcallback関数ですが、まずは表示エリアでOSCを受け取ったらどうするかを書いてみます。
function onReceiveOSC(message)
local path = message[1] -- OSCアドレス
local args = message[2] -- OSCメッセージ
local t = json.toTable(args[1].value)
self.values.text = t.data
end
これで、self(自分自身=このLabel)のvalues.text(テキストの値)を取得したJSONの[data]にすることができました。
ここでOSCのArgumentsを外しておかないと、スクリプトで書き換えられずにJSONデータが表示され続けてしまいます。

関数を追加
せっかくのluaスクリプトですし、秒だけで表示するのも見にくいので、HH:MM:SS.sで表示できるようにしてみます。
function onReceiveOSC(message)
local path = message[1] -- OSCアドレス
local args = message[2] -- OSCメッセージ
local t = json.toTable(args[1].value)
- self.values.text = t.data
+self.values.text = formatTime(t.data)
end
+function formatTime(sec)
+ if type(sec) ~= "number" then
+ return "--:--:--.-"
+ end
+ local h = math.floor(sec / 3600)
+ local m = math.floor((sec % 3600) / 60)
+ local s = sec % 60
+ return string.format("%02d:%02d:%04.1f", h, m, s)
+end
formatTime(sec)という秒を引数にした関数をあらたに作って、HH:MM:SS.sを返すようにしました。数値以外が来た場合に備えて、未取得時は --:--:--.- を返すようにしています。
見やすくなりました。

スクリプトをどこに書くか
いま、個別のLabel(表示対象)にスクリプトを書いてみたわけですが、これをすべてのLabelに対して行うのは手間がかかりますし、同じような処理を多くの箇所に書かなくてはいけないという最大の弱点があります。
touchOSCでは各コントローラそれぞれにスクリプトを書くことができますが、それ以外にドキュメントそのもの(Document Treeのトップ=ROOT)にも書くことができます。
たとえば、先ほどと同じ内容をrootに書き直すとこのようになります。
function onReceiveOSC(message)
local path = message[1]
local args = message[2]
+ if path == "/reply/cue/selected/duration" then
local t = json.toTable(args[1].value)
- self.values.text = formatTime(t.data)
+ self.children.label4.values.text = formatTime(t.data)
end
end
function formatTime(sec)
if type(sec) ~= "number" then
return "--:--:--.-"
end
local h = math.floor(sec / 3600)
local m = math.floor((sec % 3600) / 60)
local s = sec % 60
return string.format("%02d:%02d:%04.1f", h, m, s)
end
取得アドレスによって場合分けし、テキストの書換先も自身ではなく、自身の子の[label4]を書き換えています。
このように場合分けを書き足していき、このような表示ができあがりました。

- 再生中のキュー
numberdisplayNameformatTime(duration)
- 選択中のキュー
numberdisplayName-
formatTime(duration)
が表示できました。
再生バーを作りたい
せっかく再生状態までGETできたので、時間表示をつくってみます。
再生状態を示すバーは、FADERを横むきにして使います。
function update()にかくと毎フレームごとに実行されるので、getMillis()で時間を取得して、約0.1秒ごとに時間を進め、経過時間の割合を求めてフェーダーのxの値を更新します。
この時点では、「再生開始時刻」と「duration」が分かっていれば、表示側の時計で十分だろうと考えて、こういう実装を行いました。
local nowMillis = getMillis() - startMillis
local elapsedSec = nowMillis / 1000
local remainTime = runningDuration - elapsedSec
local progress = elapsedSec / runningDuration
self.children.runningTime.values.x = progress
self.children.elapsedTime.values.text = formatTime(elapsedSec)
self.children.remainTimeLabel.values.text ="-"..formatTime(remainTime)
ここまで作ってみて感じた限界
これまで/listenで得られる情報をもとに作り進めてきましたが、同時に弱点がたくさん明らかになりました。
再生中のキューが増えるとダメ
プログラム経験のある方なら既にお気づきかもしれませんが、この方法は1つのキューが別々で止まらずに動くときは問題なく動作しているように見えます。
しかしながら、再生・停止の状態しかわかららない上、複数が同時に走った場合OSCメッセージが同時にたくさんきます。
そうなると、表示されるものもその時次第ということになります。
また、停止すると表示を空にするという処理を実装していたのですが、そうすると、1と2が同時に走っていて1が先に停止した場合、停止イベントを受けて表示が空になってしまい、再生が続いているのに表示が消えてしまいました。
これは、表示側が「今どのキューが再生中なのか」という状態を持たず、イベント発生により送られるOSCだけに依存していたためです。
再生位置をずらすとダメ
再生中に再生位置を動かしても、イベントは通知されないため、表示側の時計に頼るシステムでは当たり前に無理でした。
ここまで作ってみて分かったのは、問題は touchOSC や Lua の書き方ではなく、
- 表示側が「状態」を持っていないこと
- イベントとローカル時計に依存していること
そのものだった、という点です。
つまりこの構成のままでは求めているものは絶対に得られず、すなわち/listen ベースの設計を根本的に見直す必要があることが分かりました。
そして、QLab Remoteなどの挙動を調べた結果 /updateベースで設計するという方法をとり、いちから作り直すことにしました。
ちなみに
ここまでに費やした時間、約半日。

