Slide 1

Slide 1 text

Programming with a DJ Controller — not vibe coding [email protected]

Slide 2

Slide 2 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) Tochigi Ruby Meetup (a.k.a toRuby) Pokémon TCG, WCS 2010 Tochigi Pref. winner Founder of ninja-testing.com (ド)忍者式 2

Slide 3

Slide 3 text

ʢυʣ೜ऀࣜ ߹ಉձࣾ೜ऀࣜ ~ ࣾ಺޲͚ߨԋͱ͔૬ஊͱ͔ ~

Slide 4

Slide 4 text

5FYU

Slide 5

Slide 5 text

DJ Controller DJ , CDJ, PCDJ Controller - USB MIDI - Mac - DJ Software DJ2GO2 Touch I bought it, but I got bored with it after a few weeks. 買ってみたけどすぐ飽きた 5 2024 https://www.amazon.co.jp/dp/B082L3XQGR

Slide 6

Slide 6 text

6 How to make the Groovebox How to make the Groovebox 2025 asonas's talk

Slide 7

Slide 7 text

UniMIDI I missed the talk, so I checked out his repository UniMIDI looks easy to use 7 2025

Slide 8

Slide 8 text

Agenda Programming with the DJ2GO2 Touch Using a DJ controller as a human interface device Controlling two text editors Left-hand controller Debugging Ractor applications 8 https://www.amazon.co.jp/dp/B082L3XQGR

Slide 9

Slide 9 text

MIDI Musical Instrument Digital Interface, 1981 5pin DIN → USB, Bluetooth message note on/off 9 note on ch1 C#2 80 note off ch1 C#2 control pitch...

Slide 10

Slide 10 text

DJ Controller Channel and note numbers are used to identify the button note番号と名前の対応表を見つけたが、その名前がどのボタンかわからなかった.. 10 note on ch1 C#-2 127 note off ch1 C#-2 control ... Demo

Slide 11

Slide 11 text

note number ノート番号とデバイスのコントローラーの名称の対応表を見つけたが、その名称とデ バイス上のコントローラーの関係がわからない→結局全部操作して確かめた  11 Note On message [148, 1, 127]

Slide 12

Slide 12 text

note number ノート番号とデバイスのコントローラーの名称の対応表を見つけたが、その名称とデ バイス上のコントローラーの関係がわからない→結局全部操作して確かめた  12 Control message value: 1 or 127 CW / CCW [176, 6, 1]

Slide 13

Slide 13 text

note number ノート番号とデバイスのコントローラーの名称の対応表を見つけたが、その名称とデ バイス上のコントローラーの関係がわからない→結局全部操作して確かめた  13 Control message value: 0,,127 [191, 8, 60]

Slide 14

Slide 14 text

LED blinking Send a note-on message to the DJ controller, and the LED will blink Lチカ 14 note on ch1 #C-2 1 Demo

Slide 15

Slide 15 text

note number ノート番号とデバイスのコントローラーの名称の対応表を見つけたが、その名称とデ バイス上のコントローラーの関係がわからない→結局全部操作して確かめた  15 LED Blinking!! [148, 1, 2]

Slide 16

Slide 16 text

How to get the current value Not included in standard MIDI But DJ software can query this information 初期状態(現在の状態)を知るプロトコルがない!?でもDJソフトはできてるぞ?? どうやってるんだ 16

Slide 17

Slide 17 text

Sniffing MIDI communications DJ software startup sequence SysEx F0 00 20 7F F7 SysExを送ると、操作された時と同じメッセージを一度に送ってくる 17 LED light off

Slide 18

Slide 18 text

UniMIDI is a bit difficult to use フロー制御がどこか弱い。一度にたくさんメッセージが届いたとき、メッセージの区 切りがおかしくなったりする。それをトリートメントするライブラリを間にいれた。 18

Slide 19

Slide 19 text

MidiM::Treatment Message normalization iterator Get the messages one by one メッセージを一つずつ取り出す。Fiber使ってる 19 e = MidiM::Treatment.reader(UniMIDI::Input.first) loop do it = e.gets pp it end

Slide 20

Slide 20 text

Controlling two text editors フォーカスの移動なしに同時にスクロール 20 Demo

Slide 21

Slide 21 text

Implementation: Controlling two text editors Control two text editors 急いでqueueに積む係と、ひまになったら処理する係。全てのイベントを処理する 21 # .textbringer.rb require 'drb' DRb.start_service ro = DRbObject.new_with_uri('druby://localhost:8085') queue = Queue.new ro.connect(queue) module Textbringer class Controller def self.execute_keyboard_macro(ary) c = current c.next_tick { c.execute_keyboard_macro(ary) } end end end Thread.new do while true Textbringer::Controller.execute_keyboard_macro([queue.pop]) end end 😟

Slide 22

Slide 22 text

Implementation: Controlling two text editors このデモでは:up, :downだけ 22 class DJ2GO2Server def initialize @chan = [nil, nil] end def connect(queue) if @chan[0].nil? @chan[0] = queue elsif @chan[1].nil? @chan[1] = queue else false end end def push(data) case data when [176, 6, 1] @chan[0]&.push(:up) rescue @chan[0] = nil when [176, 6, 127] @chan[0]&.push(:down) rescue @chan[0] = nil when [177, 6, 1] @chan[1]&.push(:up) rescue @chan[1] = nil when [177, 6, 127] @chan[1]&.push(:down) rescue @chan[1] = nil end end end

Slide 23

Slide 23 text

Controlling two text editors Textbringer is highly customizable The application only accepts user input at a safe stage Because it processes all messages, it isn't very smooth 23

Slide 24

Slide 24 text

Left-hand controller Photo-editing software 滑らかに動く!と思う! 24 Demo

Slide 25

Slide 25 text

Left-hand controller impl. Pixelmetor Pro + AppleEvent + MIDI Compress events to make them feel smoother. Two approaches to event compression 25

Slide 26

Slide 26 text

note number C ノート番号とデバイスのコントローラーの名称の対応表を見つけたが、その名称とデ バイス上のコントローラーの関係がわからない→結局全部操作して確かめた  26 Use the latest value Use the cumulative value Two approaches to event compression

Slide 27

Slide 27 text

Implementation: Left-hand controller Driq#readpartial 主に二つのスレッドでできている 27 Driq ♪ ♪ ♪♪ ♪ ♪ ♪♪ ♪♪♪♪♪♪♪ ♪ ♪ ♪ readparital write

Slide 28

Slide 28 text

Implementation: Left-hand controller Driq#readpartial 余裕がある時に全部読む 28 Driq ♪ ♪ ♪♪ ♪ ♪ ♪♪ ♪♪♪♪♪♪♪ ♪ ♪ ♪ readparital write def update event = @queue.readpartial(@cursor, 100) @cursor = event.last.first value = {} delta = {} event.each do |k, v| case v[1] in Integer value[v[0]] = v[1] in Float delta[v[0]] ||= 0 delta[v[0]] += v[1] else end end value.each do |k, v| @app.documents.first.layers.first.color_adjustments.send(k).set(normalize(k, v)) end delta.each do |k, d| org = @app.documents.first.layers.first.color_adjustments.send(k).get v = (org + d).clamp(ColorAdjustment[k]) @app.documents.first.layers.first.color_adjustments.send(k).set(v) end end def run_updater Thread.new do while true update end end end Block until data is available, then read up to 100 items.

Slide 29

Slide 29 text

Implementation: Left-hand controller Driq#readpartial イベントを圧縮してPixelmetorを制御する 29 Driq ♪ ♪ ♪♪ ♪ ♪ ♪♪ ♪♪♪♪♪♪♪ ♪ ♪ ♪ readparital write def update event = @queue.readpartial(@cursor, 100) @cursor = event.last.first value = {} delta = {} event.each do |k, v| case v[1] in Integer value[v[0]] = v[1] in Float delta[v[0]] ||= 0 delta[v[0]] += v[1] else end end value.each do |k, v| @app.documents.first.layers.first.color_adjustments.send(k).set(normalize(k, v)) end delta.each do |k, d| org = @app.documents.first.layers.first.color_adjustments.send(k).get v = (org + d).clamp(ColorAdjustment[k]) @app.documents.first.layers.first.color_adjustments.send(k).set(v) end end def run_updater Thread.new do while true update end end end event compression controlling Pixelmetor Pro

Slide 30

Slide 30 text

Debugging Ractor applications Logging with sound Suspend and resume a Ractor with a DJ controller Listen to N-Queens music 複数ワーカーのログを読むのはごちゃごちゃしてたいへん!音にしてみればいいのでは?? ワーカーの停止や再開をDJコントローラーで操作するよ。NQueensの音楽聴いて! 30 Demo

Slide 31

Slide 31 text

Debugging Ractor applications 31 Demo NQueens Logger Debugger MIDI MIDI dRuby Ractor Ractor.new(rinda) do |ts| while true ts.break('nq_loop') sym, size, r1, r2 = ts.take([:nq, Integ ts.log([:nq_begin, r1, r2]) ts.write([:nq_ans, size, r1, r2, NQueen ts.log([:nq_end, r1, r2]) end end def take_a(rinda, size) found = 0 size.times.reverse_each do |r1| size.times.reverse_each do |r2| tuple = rinda.take( [:nq_ans, size, r1, r2, nil]) rinda.log(tuple) found += tuple[4] end end found end

Slide 32

Slide 32 text

Debugging Ractor applications ワーカーは音程でRactorを、マスターはドラムで結果の違いを表現してるよ! 32 Demo NQueens Logger Debugger MIDI MIDI dRuby Ractor Ractor.new(rinda) do |ts| while true ts.break('nq_loop') sym, size, r1, r2 = ts.take([:nq, Integer, Integer, Integer]) ts.log([:nq_begin, r1, r2]) ts.write([:nq_ans, size, r1, r2, NQueen.nq2(size, r1, r2)]) ts.log([:nq_end, r1, r2]) end end def take_a(rinda, size) found = 0 size.times.reverse_each do |r1| size.times.reverse_each do |r2| tuple = rinda.take( [:nq_ans, size, r1, r2, nil]) rinda.log(tuple) found += tuple[4] end end found end

Slide 33

Slide 33 text

Agenda Programming with the DJ2GO2 Touch Using a DJ controller as a human interface device Controlling two text editors Left-hand controller Debugging Ractor applications 33 https://www.amazon.co.jp/dp/B082L3XQGR

Slide 34

Slide 34 text

TupleSpace for Ractors using Ractor::Port [email protected] One more thing..

Slide 35

Slide 35 text

Rinda::TupleSpace, dRuby 35 TupleSpace tuple Thread / Process

Slide 36

Slide 36 text

TupleSpace for Ractor using Ractor::Port 36 Port Port Port Ractor

Slide 37

Slide 37 text

TupleSpace4Ractor メソッド呼び出しのように書ける!べんり! 37 Port Port Port Ractor class TupleSpace4Ractor def initialize @ractor = Ractor.new { Impl.new.main_loop } log_init end Aether = self.new end Aether.write(['hello', 'world']) Aether.take(['hello', nil])

Slide 38

Slide 38 text

TupleSpace4Ractor is DRb-aware dRubyで別プロセスからも使える!Ractorなのに! 38 Port Port Port Ractor another process dRuby

Slide 39

Slide 39 text

Add logging and debugging support 39 Port Port Port Ractor logger / debugger dRuby

Slide 40

Slide 40 text

Debugging Ractor applications Logging with sound Suspend and resume a Ractor with a DJ controller Listen to N-Queens music 複数ワーカーのログを読むのはごちゃごちゃでたいへん!音にしてみればいいので は?? ワーカーの停止や再開をDJコントローラーで操作するよ。NQueensの音楽聴いて! 40 Demo

Slide 41

Slide 41 text

Debugging Ractor applications 41 Demo NQueens Logger Debugger MIDI MIDI dRuby Ractor Ractor.new(rinda) do |ts| while true ts.break('nq_loop') sym, size, r1, r2 = ts.take([:nq, Integ ts.log([:nq_begin, r1, r2]) ts.write([:nq_ans, size, r1, r2, NQueen ts.log([:nq_end, r1, r2]) end end def take_a(rinda, size) found = 0 size.times.reverse_each do |r1| size.times.reverse_each do |r2| tuple = rinda.take( [:nq_ans, size, r1, r2, nil]) rinda.log(tuple) found += tuple[4] end end found end

Slide 42

Slide 42 text

Debugging Ractor applications ワーカーは音程でRactorを、マスターはドラムで結果の違いを表現してるよ! 42 Demo NQueens Logger Debugger MIDI MIDI dRuby Ractor Ractor.new(rinda) do |ts| while true ts.break('nq_loop') sym, size, r1, r2 = ts.take([:nq, Integer, Integer, Integer]) ts.log([:nq_begin, r1, r2]) ts.write([:nq_ans, size, r1, r2, NQueen.nq2(size, r1, r2)]) ts.log([:nq_end, r1, r2]) end end def take_a(rinda, size) found = 0 size.times.reverse_each do |r1| size.times.reverse_each do |r2| tuple = rinda.take( [:nq_ans, size, r1, r2, nil]) rinda.log(tuple) found += tuple[4] end end found end