解説「照らす技術」をRubyで照らす #kanrk09
2026年7月20日
7月18日(土)に大津市伝統芸能会館で開催された関西Ruby会議09にて、「『照らす技術』をRubyで照らす」という題で登壇させていただきました。今回の発表はデモが多く、スライドもアニメーションを多用しておりそのまま公開するのが難しいです。また、発表中にふれられなかった要素がたくさんあるので、当日用意していたスクリプトをもとに、そのへんもあわせてテキストで紹介したいと思います。Markdown形式のスライドはGitHubに公開しています。
今回の記事もまあまあ長いので、ルビが要らない方は右上のボタンから消すと読みやすいかもしれません。
Harucom
さて、今回の発表でもノートパソコンは使わず、わたしがつくっているHarucomを使って発表しました。HarucomはPicoRubyでつくっている小さなRubyコンピュータで、モニターとキーボードをつなぐだけでIRBが動いて、Rubyでグラフィカルなアプリケーションを動かすことができます。
RubyKaigi 2026で発表したときからさらに開発を進めていて、エディターの動きがスムーズになったり、音声出力にのっていたノイズが消えてクリーンになったり、発表中に画面が見やすいように2倍解像度で表示できるようになったり、Ctrl-zでサスペンドできたり、Ctrl-oでIRBにスクリプトファイルをロードできたり、今回あまりふれていないところでもいろいろな改善を進めています。
また、今回の発表のためにPicoRabbitの日本語描画もつくってみました。発表に使ったブランチをpushしているので、このへんをみればどうやるかわかると思います。HarucomはBOOTHで販売しているので、もしご興味あれば買ってもらえるとうれしいです。
照
わたしは音楽ライブにいくと、ステージが建て込まれていて、ラインアレイがつながれていて、PAが組まれているんですけど、そういう機材をながめながら、開演にむけてテンションをあげていくようなことをやっています。今回のテーマは「照」ということで、舞台機材のなかでも普段は光が直接あたることのない照明にスポットライトをあててみたいとおもいました。
こういった舞台照明の装置にもソフトウェアが組込まれていて、それぞれが通信することで、遠隔で複雑な動きを実現しています。目にすることがあってもその仕組みまで理解したことがないものでも、近づいてみればおもしろい発見があるかもしれません。
舞台照明のしくみ
さて、舞台照明では、光をだすライトそのもののことを「灯体」と呼びます。この会場でも後方にいくつかPARライトが設置されていますが、こういった灯体はただの電球なので、暗くしたり明るくしたり、それ自体で明るさを調整することはできません。
そこで、明るさを調整するために、調光器と呼ばれる機械を使います。実際の照明の操作はステージ裏だったり、ライブだと客席の後方にブースを建て込んでそこから行うので、調光器を操作するためにいわゆる「卓」と呼ばれる調光卓を使います。
トライアック調光
さきほど触れたように、PARライトに使われているのは普通の電球なので、交流電源、つまり一般的な100V電源で光らせます。そのため、明るくしたり暗くしたりするためには、電球に供給する電力を調整してあげる必要があります。
このような調光にトライアックと呼ばれる素子を使っていたのでトライアック調光と呼ばれます。交流波形の一部を落とすことで、灯体に流れる実効電力を制御して、それによって明るくしたり暗くしたりする仕組みです。
LED照明
最近は電球を使うPARライトではなく、LEDを使った照明を使うことが多くなりました。LEDだと電球より省電力で発熱もおさえられます。また、色を変えられたり、さまざまな形状があったりと、いろいろな機能をもっています。
今日会場にもってきたムービングライトもLED照明です。こういったLED照明の多くは調光器を内蔵しているので、電源をつなぐだけで単体で使うことができ、外部から操作可能になっています。
Rubyで照らす
さて、今回のタイトルは「Rubyで照らす」ということで、このLED照明をどのように光らせるのか、Rubyを使ってみていこうと思います。ふだん触れることのない、難しくみえる照明の技術もRubyであれば、なんとなくわかった気になれるかもしれません。使っているコードはHarucomだけではなく、ふつうのPicoRubyでもうごくはずなので、Raspberry Pi PicoやM5Stackでも試せるはずです。
Lチカ
当日の発表では時間の関係でスキップしてしまったのですが、PicoRubyでLEDを光らせるといえばまずはLチカです。PicoRubyでLチカするには picoruby-gpio を使います。HarucomのIRBで次のスクリプトを実行してみてください。赤いLEDがチカチカ点滅するはずです(Raspberry Pi Picoの場合は1を25に変える必要があります)。
require "gpio"
led = GPIO.new(1, GPIO::OUT)
loop do
led.write 1
sleep 0.5
led.write 0
sleep 0.5
end
コンストラクタの引数には物理ピンとGPIOのモードを設定します。上のスクリプトだとGPIOの1番ピンを出力モードで初期化します。Harucomには GPIO01 (赤)と GPIO23 (緑)のLEDがついています。Raspberry Pi Picoの場合は GPIO25 です。
出力するには GPIO#write メソッドを使います。
GPIOでは 1 を書き込むとそのピンをHIGH (電源電圧、Raspberry Pi Picoではふつう3.3V) に、 0 を書き込むとそのピンをLOW (GND = 0V)にします。
以下の回路図のようにLEDのアノードがチップの出力ピン、カソードがGNDにつながっているので、出力ピンをHIGHにしてあげると電流が流れて光るようになります。
ちなみに true と false を使わず、1 と 0 を使う理由は、出力するか/しないかという論理値を表しているからではなく、ピンの出力ビットを 1 にするか 0 にするかという処理を表しているからです。
ほかにも 1 と 0 を使っていればビット演算を使って複数ビットをまとめて操作できる利点もあります。
PWM (Pulse Width Modulation)
これでLEDをチカチカさせることができるようになりましたが、これだけだとLEDを明るくしたり暗くしたりすることができません。LEDの場合はさきほどやったチカチカを高速で繰り返すことで調光を実現します。 この方法に使われているのがPWMです。PWMでは一定の周波数でON/OFFを高速に繰り返し、ONの時間の割合を変えることで明るさを調整します。このONの割合のことをduty比と呼びます。
Rubyスクリプトのようなソフトウェアで高速にチカチカさせるのは難しいですが、マイコンチップにはハードウェアPWM機能がついていてPWM制御を実現できるようになっています。
PicoRubyでは picoruby-pwm を使うことで簡単にハードウェアPWMを利用できます。ループ内でduty比を変えていくだけで、ふわっと光るLEDをつくることができます。
require "pwm"
pwm = PWM.new(1, frequency: 1000, duty: 0.0)
loop do
0.upto(100) do |i| # 0 -> 100
pwm.duty((i ** 2) / 100.0)
sleep 0.015
end
100.downto(0) do |i| # 100 -> 0
pwm.duty((i ** 2) / 100.0)
sleep 0.015
end
end
調光の制御
PWMを使うことでLEDを明るくしたり暗くしたりすることができることはわかりました。実際の調光の制御では調光卓からたくさんのLED照明を操作する必要があります。調光卓と照明の間には距離があるのでなるべく少ない配線で通信を実現したいです。また、たくさんの種類の灯体を組み合わせることで理想の照明を実現します。メーカーごとに互換性がないとそれぞれの卓が必要になってしまいます。これらの問題を解決するため、米国劇場技術協会によって約40年前に制定されたのがDMX512という通信規格です。
DMX512: Digital Multiplex 512
DMX512では512chの制御信号を多重化(Multiplex)して送ります。成瀬さんの発表でも出てきましたが、512chのデジタル信号を多重化して送るのでDigital Multiplex 512 = DMX512です。この512chのまとまりを1 Universe (宇宙)と呼びます。
灯体はデイジーチェーンで数珠つなぎに接続し、それぞれの灯体に開始アドレスを設定します。それぞれの灯体は自分のアドレスから必要なチャンネル数ぶんの値を読みます。たとえば13chの制御信号を使う灯体をアドレス1に設定すると1-13chを、次の灯体はアドレス14に設定し14-26chを使うように割り当てます。
RS485
DMX512の電気的な通信規格にはRS485というバランス接続のシリアル通信規格が採用されています。FAなどの工場でよく使われている規格で、おなじような規格だと、RS232Cとかは聞いたことがあるかもしれません。
RS485ではD+とD-の2つの信号線で反転させたデータを送信する差動信号を使います。外部ノイズがのったとしても2本の信号間で相殺されるのでノイズに強い安定した通信ができるため、最大で1200m、32台の機器を接続できます。
UART
RS485の上を流れるデータにはUARTが使われます。UARTはマイコンどうしのシリアル通信規格としてよく使われるので、電子工作をやっていたら聞いたことがあるかもしれません。UARTではその通信速度(baud rate)とデータ転送フォーマットを設定できます。DMX512の場合は250kbps、パリティがない8N2の設定が使われます。 つまり、1bitのスタートビット、8bitのデータ、2bitのストップビットで通信します。
1 bit 8 bit 2 bit
|start| b0 b1 b2 b3 b4 b5 b6 b7 |stop|stop|
DMXパケット
DMX512では512chのデータをひとまとめに送信しますが、UARTにはパケットの概念がありません。そこで、DMX512ではBREAKとMABという信号をつくりだすことでパケットの区切りをつくります。
|BREAK|MAB|SC| 1 | 2 | 3 | ... |512|
- BREAK: 最低88マイクロ秒の間LOWにする
- MAB (Mark After Break): 最低8マイクロ秒の間HIGHにする
- SC (Start Code): 1byteのスタートコード。調光データは0x00
BREAKで「フレームのおわり」、MABで「フレームのはじまり」という区切りをつくり、スタートコードに続けて512個のチャンネルデータを送ります。これを繰り返すことで照明を操作します。
ムービングライト
今回デモにはSHEHDS LED Spot 80Wというムービングライトを使いました。このムービングライトは10chと13chモードが使えますが、13chの場合パラメーターの割り当ては次のようになっています。
ch 1 Pan
ch 2 Pan fine
ch 3 Tilt
ch 4 Tilt fine
ch 5 Speed
ch 6 Dimmer
ch 7 Strobe
ch 8 Color wheel
ch 9 Gobo wheel
ch 10 Focus
ch 11 Prism
ch 12 Motor auto
ch 13 Function
DMXパケットを送信する
今回の構成は以下のようになっています。
HarucomとM5Stack DMX512ユニット間はふつうのロジックレベルのUARTで通信します。M5Stack DMX512ユニットには絶縁型のRS485トランシーバーICがのっていて、XLRケーブルでムービングライトと直接接続できます。
UARTで送ってみる
さきほど説明したとおりDMXのデータはUARTで通信されるので、picoruby-uart を使えばデータを送信できます。スクリプトは次のようになります。
uart = UART.new(unit: :RP2040_UART1,
txd_pin: 20, rxd_pin: 21,
baudrate: 250_000, stop_bits: 2)
universe = "\x00" * (512 + 1) # SC + 512ch
universe[3] = 38.chr
universe[14+3] = 38.chr
universe[6] = 100.chr #
universe[14+6] = 100.chr
loop do
uart.break 1
uart.write universe
uart.flush
end
1 universeぶん、つまり512chとスタートコードで513byteの文字列を用意し、これを送信するだけです。picoruby-uart では UART#break メソッドを使うことでBREAK信号を送信できます。
ただし、この方法ではUARTの送信がブロッキングになってしまうのでデータを送り続けている間ほかのことが何もできなくなってしまいます。そこでDMAを使ってDMXパケットを送信する picoruby-dmx というmrbgemをつくりました。
picoruby-dmx
picoruby-dmx では、C言語側でuniverseのバッファを確保しておき、タイマーとDMAが約40fpsでDMXパケットを送信し続けます。これによって、Ruby側はバッファに値を書き込むだけでDMXを送信することができるようになります。
require "board/dmx"
dmx = Board::DMX.new
dmx.start
loop do
t = Machine.board_millis / 1000.0
[1, 14].each_with_index do |b, i|
dir = i.zero? ? 1 : -1
dmx[b] = 171 + (40 * Math.sin(t * 0.7) * dir).to_i # Pan (171=正面)
dmx[b + 2] = 41 + (29 * Math.sin(t * 1.1)).to_i # Tilt (12〜70)
dmx[b + 5] = 255
end
dmx.keepalive
sleep_ms 20
end
デッドマンスイッチ
DMXで制御する照明は信号が停止してもその状態を維持する実装になっていることが一般的です。これは急にケーブルが抜けてしまったときに状態を維持してくれるという意味では良い実装ですが、プログラムがハングしてもムービングライトが明かりをつけたまま固まってしまいます。そこで、ループをまわしている間は DMX#keepalive メソッドを呼び続け、呼び出しが止まったら自動でブラックアウトするデッドマンスイッチを実装しました。
シーケンサー「序破急」
さて、ここまででPicoRubyからDMX512で照明を操作することができるようになりました。実際にライブで使うにはより複雑な動きをプログラミングし、音と光を同期させて動かす必要があります。そこで、音と光を同時に扱えるシーケンサーを実装しました。これを序破急と呼びます。
序破急はRubyKaigi 2026でasonasさんが発表していたstrudel-rbをもとにしており、スクリプトで照明と音のパターンを記述でき、REPLで演奏することができます。
たとえば、4つ打ちにあわせて光らせるなら次のようなかんじです。
fixture :s1, "shehds_80w_led_spot_light", mode: "13ch", address: 1
fixture :s2, "shehds_80w_led_spot_light", mode: "13ch", address: 14
tempo 120
track(:strobe) { dmx(:s1).dimmer("[1 0]*4").color("<red blue green>") }
track(:wash) { dmx(:s2).dimmer(sine.slow(2)).pan(sine.range(0.3, 0.7).slow(8)) }
track(:drums) { sound("bd*4, ~ sd ~ sd, hh*8") }
灯体の定義にはOpen Fixture LibraryのJSONを使っていて、dimmerやpanといったパラメーターをチャンネルにマッピングできるようになっています。
QOA: Quite OK Audio
さて、本来のデモはシーケンサーでおわりだったのですが、出囃子に桃井はるこさんの「ルミカ」という素敵な曲を使わせていただいたので、せっかくならもうすこし長くみなさんに聴いてもらいたいと思いました。
実は内部生成するドラム音源以外にサンプリング音も扱えるようにQOAとWAVの読み込み機能もHarucomにつくっていました。そこで、このQOAデコーダーを使い、曲をまるっと再生できるような機能を序破急に組込みました。
QOAは整数演算だけでデコードできる超軽量のオーディオフォーマットです。リファレンス実装はC言語で400行くらいしかありません。HarucomのFlashは2MBほどの空き領域があったので、ここをフルに使って1番のみに編集した「ルミカ」をぎゅうぎゅうに詰め込みました。
照明プログラムは序破急の機能を使い、ビートにあわせて光らせたり、イントロ、Aメロ、Bメロ、サビでそれぞれ再生パターンが変化するようになっています。サビではムービングライトのプリズムも使い、能楽堂全体を光らせるようにしたのですが、うまくサビのタイミングできまって良かったです。
スクリプトをみると小節ごとにパターンを切り替えている様子がよくわかると思います。
たとえば、以下の配列で照明の動きを定義していて、曲のはじまりから11小節がイントロ、その次の23小節がAメロ/Bメロ、そのあとの39節がサビからラストにかけての動きになっています。
pan_left = [
[hold(0.667), 11],
[sine.range(0.60, 0.73).slow(8).segment(16), 23],
[sine.range(0.60, 0.73).slow(2).segment(16), 39],
[hold(0.667), 1],
[rest, 286],
]
pan_right = [
[hold(0.667), 11],
[sine.range(0.73, 0.60).slow(8).segment(16), 23],
[sine.range(0.73, 0.60).slow(2).segment(16), 39],
[hold(0.667), 1],
[rest, 286],
]
tilt_path = [
[hold(0.141), 11],
[hold(0.141), 23],
[cosine.range(0.0, 0.3).slow(2).segment(16), 39],
[hold(0.141), 1],
[rest, 286],
]
prism_path = [
[hold(0), 34],
[hold(180), 39],
[hold(0), 1],
[rest, 286],
]
さいごにスポットライトがスクリーンを向くところは、trackのミュートを切り替えてライブで動かしていました。ちょっと遅れてしまったけど、こういうのをリアルタイムにできるのが序破急のよさです。
まとめ
舞台照明はふだんの生活で触れることがない世界ですが、こういったよくわからないものについていろいろと話しているときって一番楽しいですよね。今回の発表では能舞台を光らせるっていう、これまでにない体験ができました。なにかよくわからないけど、なんか楽しそうに話しているのが伝わったらいいな、と思っています。今週末の土曜日、7月25日には大阪で桃井はるこさんのワンマンライブがあるのでまた関西に行く予定です!