Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Programming with a DJ Controller — not vibe coding

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Programming with a DJ Controller — not vibe coding

RubyKaigi 2026

Avatar for seki at druby.org

seki at druby.org

April 23, 2026

More Decks by seki at druby.org

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 6 How to make the Groovebox How to make the

    Groovebox 2025 asonas's talk
  4. UniMIDI I missed the talk, so I checked out his

    repository UniMIDI looks easy to use 7 2025
  5. 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
  6. 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...
  7. 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
  8. 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
  9. How to get the current value Not included in standard

    MIDI But DJ software can query this information 初期状態(現在の状態)を知るプロトコルがない!?でもDJソフトはできてるぞ?? どうやってるんだ 16
  10. Sniffing MIDI communications DJ software startup sequence SysEx F0 00

    20 7F F7 SysExを送ると、操作された時と同じメッセージを一度に送ってくる 17 LED light off
  11. 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
  12. 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 😟
  13. 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
  14. 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
  15. Left-hand controller impl. Pixelmetor Pro + AppleEvent + MIDI Compress

    events to make them feel smoother. Two approaches to event compression 25
  16. 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.
  17. 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
  18. Debugging Ractor applications Logging with sound Suspend and resume a

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

    Ractor with a DJ controller Listen to N-Queens music 複数ワーカーのログを読むのはごちゃごちゃでたいへん!音にしてみればいいので は?? ワーカーの停止や再開をDJコントローラーで操作するよ。NQueensの音楽聴いて! 40 Demo
  24. 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
  25. 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