---
title: Play Music on Ruby - Picorubyで作るMIDIオーケストレーションツール -
tags:  #ruby #picoruby #midi  
author: [Toshio Maki](https://image.docswell.com/user/kirika)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/LJ1YD1QDEG.jpg?width=480
description: 2026/05/31 関ケ原Ruby会議01 発表資料
published: May 31, 26
canonical: https://image.docswell.com/s/kirika/5GNQ4D-2026-05-31-085807
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/LJ1YD1QDEG.jpg)

Picoruby
MIDI
Toshio Maki（@Kirika_K2）
音
玉
源牧雅楽頭ミュージ郎俊男


# Page. 2

![Page Image](https://bcdn.docswell.com/page/GJWGY8M872.jpg)

Omotesando.rb
SaaS
山
歯
己
子
Roppongi.rb
身
自
@Kirika_K2
Ruby kaja
Web
New!


# Page. 3

![Page Image](https://bcdn.docswell.com/page/4EZLX8Q973.jpg)

MIDoRI
RubyKaigi2026
Smart HR
HR Music Arena
Pixiv
Showcase
Ruby Illuminations
自
人
日
ANDPAD


# Page. 4

![Page Image](https://bcdn.docswell.com/page/Y76W4P6D7V.jpg)

MIDoRI
MIDoRI
MIDI on Ruby Interpreter
M5Stack CoreS3 / Tab5 で動く、MIDI プロセッサ。
PicoRuby でMIDoRIに接続された MIDI 機器を 自由に演奏 / 変換 / 中継 できる。


# Page. 5

![Page Image](https://bcdn.docswell.com/page/G75MQK6874.jpg)

MIDoRI
入力
処理
あらゆる MIDI 機器を接続し、入力をフックできる
MIDI メッセージを生成・加工し、任意の MIDI 機器に出力で
きる
設定
設定ファイルを Ruby で書ける
電子楽器が連携するシーンで、様々な用途に使える汎用 MIDI デバイス を目指している。


# Page. 6

![Page Image](https://bcdn.docswell.com/page/9J29PWMVER.jpg)

実演 1
先づ、音を鳴らし申す


# Page. 7

![Page Image](https://bcdn.docswell.com/page/DEY45LXQJM.jpg)

デモのスクリプト
MIDoRI本体
YAMAHA Reface CP
MIDoRI 本体
3 オクターブのMIDIキーボード
M5Stack Tab5
USB-MIDI
LUMI Keys
MIDoRI
(M5stack Core5)
YAMAHA SEQTRAK
グルーブボックス
MIDI-DIN
入力ルーティング + リズムシーケンスを MIDoRI が担当
PA
SEQTRAK


# Page. 8

![Page Image](https://bcdn.docswell.com/page/VJNYN41278.jpg)

SEQTRAK
Ch.1 Ch.2 Ch.3
琴
和太鼓 三味線
Ch.4
Ch.5
Ch.6
Ch.7
Ch.8
尺八
Ch.9
琴
Ch.10 Ch.11
サンプラー
2024年発売のグルーブボックス。リズム、シンセサイザー、FM、サンプラーを搭載
和太鼓、三味線、琴、尺八など、和楽器も搭載


# Page. 9

![Page Image](https://bcdn.docswell.com/page/YE9PRQ5DJ3.jpg)

音色
和太鼓＋三味線
和太鼓＋三味線＋琴＋尺八
担当パートなど
和太鼓 … SEQTRAK 内蔵シーケンサ
三味線 … MIDoRIのシーケンサで再生
MIDIキーボードの下１オクターブで琴を演奏
MIDIキーボードの上２オクターブで尺八を演奏
和太鼓＋三味線＋琴
SEQTRAKの内蔵シーケンサのみ
和太鼓＋三味線＋琴＋尺八
MIDIキーボードの上２オクターブで尺八を演奏


# Page. 10

![Page Image](https://bcdn.docswell.com/page/GE8DWGY5ED.jpg)

MIDI
主なメッセージ
中身
用途
Note On
鍵盤番号 + 強さ
「ドを強さ 100 で押した」
90 3C 64
Note Off
鍵盤番号
「ドを離した」
80 3C 00
Control Change
番号 + 値
音量・モジュレーション等
B0 07 6E
Pitch Bend
上下の量
ベンド
E0 00 40
MIDI Clock
1 バイト
テンポ同期 (24 発/拍)
● 1 メッセージは 2〜3 バイトが大半。
● 1 本の MIDI ケーブルに 16 チャンネル流せる
※ 例は ch1 / 60 (中央C) を中心とした典型値。16 進数表記。
実際のバイト列
F8


# Page. 11

![Page Image](https://bcdn.docswell.com/page/LELMNG437R.jpg)

MIDoRI
MIDI
USB-MIDI
MIDI-DIN
TRS-MIDI
USB-MIDI
MIDoRI
(CoreS3 / Tab5)
(BLE-MIDI)
MIDI-DIN
TRS-MIDI
(BLE-MIDI)
入出力のルーティング・加工を担当


# Page. 12

![Page Image](https://bcdn.docswell.com/page/4JMYXQKMJW.jpg)

MIDI::Device
物理層 (Transport)
USB-MIDI
MIDI-DIN / TRS-MIDI
どの機器でも同じ API
Ruby 層 (Abstraction)
MIDI::Device
統一インターフェース
# どの物理層でも
device = MIDI::Device.new(
MIDIDevices.USB_MIDI
)
device.note_on(0, 60, 100)
device.note_off(0, 60)
BLE-MIDI
(未実装)
Transport 層の違いを Ruby から意識せずに MIDI::Device 1 つで扱える


# Page. 13

![Page Image](https://bcdn.docswell.com/page/PJR9N8XL79.jpg)

MML
1
MIDI
UI.pad
ON / OFF
type: :toggle
ON
player.start / OFF
MIDI clock SEQTRAK
tick player.tick(t)
SEQTRAK
用
MIDI.start!
MIDI clock
文
4
# Pad 1: 三味線 MML の出力 ON/OFF トグル
Player
MML
長
3
Sequence
文
2
UI.log(&quot;Creating MML sequences...&quot;)
shamisen = MIDI::MML::Sequence.new(shamisen_mml, channel: 1)
player = MIDI::MML::Player.new(seqtrak, shamisen)
音
音
MML + UI.pad
player.stop
# ON
→ player.start (SEQTRAK の小節グリッドに同期した位置から再生)
# OFF → player.stop
(鳴っている音は all_notes_off で停止)
UI.pad(1, label: &quot;Shamisen&quot;, color: :green, type: :toggle) do |
state|
if state
player.start
UI.log(&quot;Shamisen: ON&quot;)
else
player.stop
UI.log(&quot;Shamisen: OFF&quot;)
end
end
MIDI.start!(bpm: 140, subdivisions: 24, output: seqtrak) do |t|
player.tick(t)
end


# Page. 14

![Page Image](https://bcdn.docswell.com/page/PEXQN8M6JX.jpg)

MML
Music Macro Language
# ===== MML Sequences =====
melody_mml = &lt;&lt;~MML
o4 l4
ccgg aag2 ffee ddc2
ggff eed2 ggff eed2
ccgg aag2 ffee ddc2
MML
# ===== 音色設定 =====
device.program_change(10, channel: 0) # Melody: Music Box（キラキラした音色）
# ===== シーケンス生成 =====
melody_seq = MIDI::MML::Sequence.new(melody_mml, channel: 0, velocity: 110)
player = MIDI::MML::Player.new(device, melody_seq, loop: true)
# ===== 再生ループ =====
MIDI.bpm_loop(100, output: device, subdivisions: 24) do |clock|
player.tick(clock)
end
足
SequenceでMMLをイベント情報に変換し、Playerに渡したclock値で発生させるMIDIメッセージを決定


# Page. 15

![Page Image](https://bcdn.docswell.com/page/3EK9NKGGED.jpg)

MIDI
(1
note &lt; 64
)
note ≥ 64
if e[:velocity] == 0
pair = $active_notes.delete(e[:note])
seqtrak.note_off(pair[1], 0, channel: pair[0]) if pair
next
end
SPLIT_NOTE = 64
ch7
keys_input.on(:note_on) do |e|
# velocity 0 は note_off と同義
in_note = e[:note]
if in_note &lt; SPLIT_NOTE
out_ch = LOWER_CH
raw
= in_note
else
out_ch = UPPER_CH
raw
= in_note + UPPER_OFFSET
end
# 量子化 ON なら A# ペンタトニックに丸め、OFF なら素通し
ch8
out_note = $quantize_enabled ? quantize_to_pentatonic_minor(raw) : raw
note_on
ch/note
$active_notes
note_off
if out_note &gt;= 0 &amp;&amp; out_note &lt;= 127
seqtrak.note_on(out_note, e[:velocity], channel: out_ch)
$active_notes[in_note] = [out_ch, out_note]
end
end
力
止
八
keys_input.on(:note_off) do |e|
pair = $active_notes.delete(e[:note])
seqtrak.note_off(pair[1], 0, channel: pair[0]) if pair
end


# Page. 16

![Page Image](https://bcdn.docswell.com/page/L73WVZN575.jpg)

(root = A#)
A#
B
C
=
C#
D#
ON / OFF
音
八
音
子
D
E
F
F#
G
G#
A
# =============================================================================
# A# マイナーペンタトニック量子化
# =============================================================================
# マイナーペンタトニックの度数 (root, b3, 4, 5, b7, +1oct)
PENTATONIC_MINOR_DEGREES = [0, 3, 5, 7, 10, 12]
SCALE_ROOT_PC = 10
# A# (0=C, 10=A#)
[0, 3, 5, 7, 10]
Pad 2
音
用
赤
A#
# 任意の note を最も近い A# マイナーペンタトニック音に量子化
ON
def quantize_to_pentatonic_minor(note)
return note if note &lt; 0 || note &gt; 127
rel = note - SCALE_ROOT_PC
pc
= rel % 12
base = rel - pc
best = 0
best_dist = 13
PENTATONIC_MINOR_DEGREES.each do |s|
d = (pc - s).abs
if d &lt; best_dist
best_dist = d
best = s
end
end
base + best + SCALE_ROOT_PC
end


# Page. 17

![Page Image](https://bcdn.docswell.com/page/87DK8RWYJG.jpg)

UI.pad
Ruby
三味線シーケンス、ペンタトニック量子化のオン・オフに使用
# Pad 1: 三味線 MML の出力 ON/OFF トグル
# ON
→ player.start (SEQTRAK の小節グリッドに同期した位置から再生)
# OFF → player.stop
(鳴っている音は all_notes_off で停止)
UI.pad(1, label: &quot;Shamisen&quot;, color: :green, type: :toggle) do |state|
if state
player.start
UI.log(&quot;Shamisen: ON&quot;)
else
player.stop
UI.log(&quot;Shamisen: OFF&quot;)
end
end
# Pad 2: ペンタトニック量子化の ON/OFF トグル (初期 ON)
# トグルパッドは起動時 OFF 表示で初期状態を設定できないため、押下ごとに
# 自前フラグを反転し、ラベルで実状態を示す (押すたびに必ず切り替わる)。
UI.pad(2, label: &quot;Quant ON&quot;, color: :cyan, type: :toggle) do |state|
$quantize_enabled = !$quantize_enabled
UI.pad_label(2, $quantize_enabled ? &quot;Quant ON&quot; : &quot;Quant OFF&quot;)
UI.log(&quot;Quantize: #{$quantize_enabled ? &#039;ON&#039; : &#039;OFF&#039;}&quot;)
end
行
タッチパッドを定義 → 叩かれたら登録した任意の Ruby ブロックを実行。


# Page. 18

![Page Image](https://bcdn.docswell.com/page/VJPK8W92E8.jpg)

開発の話


# Page. 19

![Page Image](https://bcdn.docswell.com/page/2EVVN8MXEQ.jpg)

2023
Keebkaigi @ 松本
PRK_Firmware を拡張してMIDIメッセージが使えるようにしたものをLT発表
2023〜
MIDI IN/OUT 両対応の自分用MIDIデバイスが欲しい
構想はあったが、RP2040 はCPU・メモリ共にスペック不足 / ESP32 への PicoRuby 移植が必要 / 入力 IF も自作が
必要など、完成までの道のりが険しかった
2025
RubyKaigi2025 で picoruby-esp32 の発表 🎯
ESP32 移植問題が解決。さらに M5Stack CoreS3 が要件を満たす機器として入手可能に → MIDoRI 開発開始


# Page. 20

![Page Image](https://bcdn.docswell.com/page/57GLK5GREL.jpg)

M5Stack CoreS3
M5Stack Tab5
メインターゲット
高スペック版
PSRAM
PSRAM
8 MB
32 MB
UI
UI
2.0インチ タッチパネル LCD
5インチ タッチパネル LCD
ESP32-S3 + PSRAM 8 MB
PicoRuby + USB Host が同居可能。
ESP32-P4 + PSRAM 32 MB。リソースに余裕あり。
LCD / SD カード / インターフェースが揃う。
USB Host 機能内蔵でCoreS3より、更に拡張性が高い。


# Page. 21

![Page Image](https://bcdn.docswell.com/page/4EQYNZXYJP.jpg)

FreeRTOSベースのアプリケーション実装の上にPicorubyを含む複数タスクを実装
ESP32-S3 / ESP32-P4 (2 コア)
Core 0 リアルタイム I/O
メインタスク (C)
USB MIDI Class Driver (C)
アプリ初期化 / 画面描画 / タッチ処理 / USB ホストイベント処理
デバイス開閉 / 転送 callback
Core 1 ロジック / スクリプト
Supervisor Task (C)
PicoRuby Task (Ruby)
VM 生成 / 破棄
● SD カードのユーザスクリプト実行 (main_task.rb)
● UI.pad / UI.log 等で UI に状態を渡す
● MIDI.bpm_loop中にコンテキストスイッチを発生させて、他のタスクと並行で動かす
MIDI Input Task (C)
受信バイトをパース → enqueue
FreeRTOS queue
esp_timer … 高分解能タイマ
MIDI Clock Timer
Note Scheduler Timer
24 PPQ で送出
1 ms 周期で note_off制御


# Page. 22

![Page Image](https://bcdn.docswell.com/page/KJ4WG36Z71.jpg)

Ruby
C
「何をどう鳴らすか」は Ruby で書きたい。
データの入出力などリアルタイム性の高い部分は C/C++ で書きたい。
C
Ruby
•
メインタスク (UI 描画 / タッチ)
•
MIDIメッセージの変換・演奏ロジック
•
USB MIDI Class Driver
•
MIDIイベントのフック
•
MIDI Input Task (受信パース)
•
UI イベントのフック
•
MIDI Clock / Note Scheduler Timer
•
Supervisor Task (VM lifecycle)
C 側で拾ったイベントを FreeRTOS キューで送って、Rubyオブジェクトに変換


# Page. 23

![Page Image](https://bcdn.docswell.com/page/LE1YD1VD7G.jpg)

問題: 普通の loopだと他のタスクに切り替わらず、フリーズする
解決策: ループの裏側でタスク切換えを行うようにした
# ❌ CPU を握りっぱなし
# ✅ MIDoRIのループ
loop do
device.note_on(0, 60, 100)
sleep 1
device.note_off(0, 60)
end
# ループ中、他のタスクに切り替わらない
MIDI.bpm_loop(120, output: device) do
device.note_on(0, 60, 100)
MIDI.sleep_ms(100)
device.note_off(0, 60)
end
# ループの裏側でコンテキストスイッチを発生
工
MIDI.sleep_ms（Cバインディング）を呼び出して、コンテキストスイッチを発生させる必要がある


# Page. 24

![Page Image](https://bcdn.docswell.com/page/GEWGY8L8J2.jpg)

Supervisor
VM
VM 新規生成
スクリプト load
PSRAM 2 MB を割当
SD カードから選択したRuby
実行
スクリプトをload
スクリプトごとに 1 VM
PSRAM 2 MB を割当て
エラーも Supervisor がハンドリング
VM 内例外で本体は落ちない。例外をハンドリングして UI に戻る、エラーはログに記録する
VM ごと破棄


# Page. 25

![Page Image](https://bcdn.docswell.com/page/47ZLX8M9J3.jpg)

1
2
3
4
5
picoruby-midi の upstream マージ
MIDoRI 以外でも MIDI が簡単に使えるように
BLE-MIDI 対応
無線 MIDI キーボード対応等
r2p2 対応
実験的スクリプトを試しやすく
USB-MIDI Device 側でも動かす
MIDoRI 自身が PC/iPad から MIDI デバイスに見えるように
UI 機能拡張
スライダ / XY パッド等のコンポーネントを揃え、Ruby DSL で自分仕様のデバイスを定義できるように


# Page. 26

![Page Image](https://bcdn.docswell.com/page/YJ6W4PNDJV.jpg)

picoruby-midi
picoruby-midi-mml
picoruby-midi
プロトコル / パーサ / Clock
Transport interface
picoruby-usb_midi_host
picoruby-uart_midi
picoruby-sam2695
MIDoRI 内部の C / Ruby コードをFreeRTOS 依存を抽象化して Picoruby本体にマージ。


# Page. 27

![Page Image](https://bcdn.docswell.com/page/GJ5MQK58J4.jpg)

picoruby-midi
Phase 1
Phase 2
Phase 3
Phase 4
内部リファクタ
API 整理
mruby バインディングの追加
スコープ整理
OS-free コア抽出
MML 分離
Phase 5
Phase 6
Phase 7
Phase 8
gem rename + USB host stack
取り込み
README / sig/*.rbs / example
整備
ホストビルドのパーサ単体テス
ト
upstream 提出


# Page. 28

![Page Image](https://bcdn.docswell.com/page/LE3WVZNGE5.jpg)

MIDoRI
Phase 1
✓ 完了
Phase 2
✓ 完了
内部リファクタ
API 整理
OS-free コア抽出
MIDI.start! / on_bpm_change追
Phase 3
✓ 完了
mruby バインディングの追加
Phase 4
✓ 完了
スコープ整理
MML 分離
加
Phase 5
✓ 完了
gem rename + USB host stack
取り込み
(main/usb_midi_host.c が ~70
行に)
Phase 6
✓ 完了
README / sig/*.rbs / example
整備
Phase 7
✓ 完了
ホストビルドのパーサ単体テス
ト
Phase 8
upstream 提出
(残作業)
残作業


# Page. 29

![Page Image](https://bcdn.docswell.com/page/8EDK8RWN7G.jpg)

実演２
picoruby-midi を MIDoRI 以外で動かす
ATOM Matrix (素の ESP32 / PSRAM 無し)


# Page. 30

![Page Image](https://bcdn.docswell.com/page/V7PK8W9NJ8.jpg)

MIDoRI (CoreS3)
ATOM Matrix
PSRAM
8 MB
無し
ホスト
USB MIDI Host
UART (GPIO 26)
音源
外部 SEQTRAK
SAM2695
ファーム
MIDoRI
R2P2-ESP32 + picoruby-midi
MIDoRI 上で動くものと、同じ picoruby-midi gem が ATOM Matrix でも走る


# Page. 31

![Page Image](https://bcdn.docswell.com/page/2JVVN8MYJQ.jpg)

MML
Music Macro Language
require &quot;midi&quot;
require &quot;midi-mml&quot;
require &quot;sam2695&quot;
sam = SAM2695.new(26,32)
device = MIDI::Device.new(sam)
# ===== MML Sequences =====
melody_mml = &lt;&lt;~MML
o4 l4
ccgg aag2 ffee ddc2
ggff eed2 ggff eed2
ccgg aag2 ffee ddc2
MML
# ===== 音色設定 =====
device.program_change(10, channel: 0) # Melody: Music Box（キラキラした音色）
# ===== シーケンス生成 =====
melody_seq = MIDI::MML::Sequence.new(melody_mml, channel: 0, velocity: 110)
player = MIDI::MML::Player.new(device, melody_seq, loop: true)
# ===== 再生ループ =====
MIDI.bpm_loop(100, output: device, subdivisions: 24) do |clock|
player.tick(clock)
end


# Page. 32

![Page Image](https://bcdn.docswell.com/page/5EGLK5GWJL.jpg)

✓ 動くは動く … 同じ Ruby ファイルが PSRAM 無しの素の ESP32 でも走る
△ メモリ的にはギリギリ … picoruby-midi 自体をもっと省メモリにする必要がある
これから
•
picoruby-midi の省メモリ化
PR提出前に、やれるだけやっておく
•
FreeRTOS 抽象の RP2040 / RP2350 への移植
今回は ESP32 系のみ。PRマージ後に他ボードへ広げる


# Page. 33

![Page Image](https://bcdn.docswell.com/page/4JQYNZXQ7P.jpg)

ご清聴ありがとうございました
github.com/kirikak2/midori
興味持っていただいた方は、是非この後お声がけください。スターもらえるとすごく嬉しいです。


