自分専用に魔改造したBMSプレイヤー beatoraja の話

はじめに
私は BMS(Be-Music Source)という「音ゲー」を遊んでいます。
画面を流れてくるノーツを音楽に合わせて叩く、ビートマニアっぽいアレ、というとイメージしやすいかもしれません。
そのBMSを遊ぶための定番プレイヤーが beatoraja(ビートオラジャ)です。
Java/libGDX 製のオープンソースソフトで、誰でも自由に改造できます。
で、その「自由に改造できる」をいいことに、私はこれを自分専用にとことん魔改造してしまいました。
配信用のオーバーレイ、画面に住むVRMアバター、AIによる曲検索、自分専用のオンラインランキング。
気づけば「BMSプレイヤー」というより「BMSを中心とした自分用プラットフォーム」みたいなものになってしまっています。
この記事では、そのフォークに入れた機能をざっくりまとめておきます。
BMSを知らない方にも雰囲気が伝わるよう用語は軽く補足しつつ、作る側として面白かった実装の話も少し混ぜていきます。

このフォークの方針
ベースの beatoraja に対して、私のフォークは思い切った割り切りをしています。
- Windows 11 専用 に絞る(自分の環境がこれなので)
- 7key BMS に特化(一番遊ぶモードだけ手厚く)
- Java 25 ランタイムを同梱して、zip / MSI でそのまま配れる形に
「みんなのための汎用プレイヤー」ではなく「自分が一番気持ちよく遊べる一台」を目指しているのが、そもそもの出発点です。
配信を「一体化」した — 内蔵オーバーレイ
最初の大きな改造が、配信まわりの一体化です。
普通、配信で「今プレイ中の曲」や「スコア」を画面に出そうとすると、別ツールを噛ませたり手作業で更新したりが必要になります。
これが地味に面倒。そこで beatoraja 本体に小さなWebサーバを内蔵させ、プレイ状況をリアルタイムに吐き出すようにしました。
具体的には、本体が localhost:7322 でHTTPサーバを立ち上げ、現在の状態を約1秒ごとに state.json として配信します。
OBS(配信ソフト)側は ブラウザソースを1枚置くだけ。
それだけで以下のような情報が自動で画面に出ます。
- ゲーム枠(プレイ画面の表示位置)
- 時計・セッション統計(今日の総ノーツ数やプレイ回数)
- プロフィール(段位や一言コメント)
- ノーツレーダー(譜面傾向のレーダーチャート)
- Now Playing(今やっている曲)
- プレイオプション(ランダム・ゲージ設定など)
- プレーヤーの心拍数(心拍計からBLE接続で取得)
URLのパラメータで表示するブロックを選べるので、「枠だけ」「統計だけ」みたいな使い分けもできます。
状態を持つJava側は ObsHttpServer / ObsState(src/bms/player/beatoraja/stream/obs/)、見た目を作るフロント側は obs/overlay.html / overlay.css / overlay.js です。
フロントは差分ポーリングで state.json を読みに行き、CSS変数でテーマを差し替えられるようにしてあります。
心拍数まで配信に乗せた
「どうせ配信するなら、自分がどれだけ焦ってるかも見せたら面白いのでは?」という発想から、心拍計連携も入れました。
外部の心拍計の値を取り込んで、オーバーレイに現在の心拍数と直近数分の推移グラフ(最小・最大つき)を表示します。
心拍がぴょこんと跳ね上がるのが見えるので、配信的にはなかなか美味しい画です。
仕組みとしては、心拍を読むための小さなネイティブ製の「橋渡しプログラム(bridge)」を子プロセスとして起動し、そこから流れてくるデータを本体が受け取ってオーバーレイに反映しています。
橋渡しプログラムが落ちても、間隔を空けながら自動で再起動するようにしてあるので、配信中に黙って死んでいた……という事故が起きにくい作りです。

stream/obs/HeartRateService.java が橋渡しプロセスを管理し、標準出力に流れてくるJSON行を読み取って状態へ反映します。
再起動は指数バックオフ(失敗するほど待ち時間を延ばす)方式にしています。
画面に住むVRMアバター(ペット)
ここからが個人的に一番テンションの上がった改造です。
プレイ状況に連動して動くVRM3Dアバターを、画面に住まわせました。
VRMというのは3Dキャラクターのデータ形式で、Clusterなどでもおなじみのアレです。
このアバターはただ立っているだけではなく、プレイの結果に反応します。
- クリアした / スコア自己ベスト更新 / フルコンボ → 嬉しそうな表情
- 落ちた(ゲージが足りずに失敗)→ 残念そうな表情
- 場面に応じて吹き出しでコメント
- 選曲・プレイ中・リザルトで立ち位置が変わる
- 何もしていないときは待機モーション
表示方法は2通り用意しました。
- OBSブラウザソース版 — 配信画面にアバターを合成したいとき向け
- Tauri透過オーバーレイ版 — デスクトップに常駐する、枠なし・背景透過・クリックすり抜け・常に最前面のウィンドウ。配信せず一人で遊ぶときでも、デスクトップの隅に相棒がいる感じになります
地味に大事なのが、VRMの読み込みに失敗してもソフトが落ちない設計にしてあること。
アバターは「いたら嬉しい」存在であって、これのせいで本体が起動しないのは本末転倒なので、読み込みエラー時はそっと隅に通知を出すだけにとどめています。

アバターWebアプリは three.js + three-vrm(
@pixiv/three-vrm)で、ビルド不要のESモジュール構成(obs/avatar/)。
待機モーションはVRMA(アニメーション形式)で差し替え可能、無ければハードコードのポーズにフォールバックします。
常駐版のTauriホストは Rust + WebView2(obs/avatar-overlay/)。
表情やコメントの動作はPlaywrightで自動テストしています。
ペット育成システム — プレイ結果で育つ相棒
そのアバターには、ペット育成のバックエンドをくっつけました。
プレイするほどに育っていく相棒、というわけです。
プレイ結果に応じて、経験値・レベル・友好度・機嫌が増減します。
さらに人格が4種類あり、
- まじめ
- 煽り気味
- マイペース
- 褒め上手
人格ごとに、同じ場面でも返してくるコメントが変わります。
煽りキャラに「うわ、また落ちたの?」みたいなことを言われると、悔しいけどちょっと笑ってしまいます。
育成データはちゃんとデータベースに保存されるので、起動し直してもリセットされません。
育成ロジックは PetGrowthService 系、コメント生成は PetCommentGenerator(状況×人格のコメント表)、永続化は PetDataStore。
ここはあえてLLMではなくルールベースで組んでいます。
コメントの方向性を完全にコントロールしたかったのと、軽さ重視のためです。(AIを使っているのは、次に紹介する曲検索のほうです。)
「皿多めで速い曲」で探せる — AI自然文曲検索
BMSをやっていると曲数がとんでもないことになり、「あの感じの曲、もう一回やりたいんだけど名前が出てこない」が頻発します。
そこで入れたのが、自然文(普通の話し言葉)で曲を探せるAI検索です。
たとえばこんな探し方ができます。
- 「レベル10〜12で皿が多めの速い曲」
- 「最近やってない未プレイの縦連譜面」
レベル・BPM(曲の速さ)・曲の長さ・プレイ頻度・最近やったかどうか、さらに譜面のパターン(乱打・縦連・トリル・階段・同時押し・皿・ソフラン〔曲中で速さが変わること〕など)まで解釈して、条件に合う曲を出してくれます。
仕組みは、ローカルで動くLLM(llama.cpp+Qwen2.5系の小型モデル)と、昔ながらのルールベース検索のハイブリッドです。
まずLLMに自然文を解釈させ、うまくいかなければルールベース(正規表現とキーワード辞書)にフォールバックする二段構え。
クラウドに投げず手元で完結するので、プライバシー的にも気楽です。

パイプラインは song/search/NaturalLanguageSongSearch、解釈は HybridQueryInterpreter(LLM→ルールへフォールバック)、推論は LlamaCppInference。
GPUレイヤ数やスレッド数、LoRAアダプタの差し込みも設定で効かせられます。
プレイ体験そのものの改善
派手な機能ばかり紹介してきましたが、地味に「プレイそのものが快適になる」改造もいくつか入れています。
判定タイミングの自動調整
人それぞれ、ノーツを「気持ち早めに押す」「遅めに押す」というクセがあります。
これを自動で計測して、ノーツ表示のタイミングを少しずつ最適化してくれる機能です。
プレイすればするほど、自分にとって一番気持ちいいタイミングに勝手に寄っていきます。
ポイントは平均ではなく中央値で補正していること。
たまの事故押し(とんでもなく外したノーツ)に引っ張られないので、補正が暴れません。
BPM追従のSUD+(緑数字を一定に保つ)
SUD+ は、ノーツが見える範囲を上から覆って調整する機能で、その効き具合の目安が通称「緑数字」です。
問題は、曲の途中で速さが変わるソフラン曲だと、この見やすさが崩れてしまうこと。
そこで、HI-SPEED固定時に曲中のBPM変化に合わせてSUD+を自動で上下させ、緑数字を一定に保つようにしました。
ソフラン曲でもノーツの落ちてくる見え方が安定します。
これは描画の計算式とカバー量の計算をきっちり一致させるのが地味に大変で、何度か作り直した思い入れのある機能です。
ランダム譜面の「リプレイ再現性」修正
BMSにはノーツの配置をシャッフルする「RANDOM」系オプションがあります。
本来、リプレイ(プレイの録画再生)では同じ配置が再現されるべきなのですが、一部のオプションが乱数の使い方の都合で再現できていないバグがありました。
これを、シード(乱数の種)からきちんと同じ譜面を再生成できるように直しました。
その他のこまごました改善
- 選曲画面に「最終プレー日時」を表示(その曲を最後にいつ触ったか分かる)
- 途中終了時のIR送信ノーツ数補正(ハード落ちしたとき、ランキングに送る総ノーツ数を「そこまでに処理した分」に直して、途中落ちスコアが変な扱いをされにくくした)
- おすすめフォルダに難易度表のレベルを表示(どの難易度表の何レベルか分かるように)
独自オンラインランキング「Izu Scorelog」
BMSには IR(インターネットランキング) という、スコアをオンラインで競う仕組みがあります。
私はこれを**自前で立てた「Izu Scorelog」**に接続しています。
Izu Scorelogに向けて譜面の特徴量を大幅に拡張しています。
標準のノーツレーダーは6軸ですが、それを縦連・トリル・階段・皿の種別・ソフランの種別……といった細かい指標まで算出して曲データベースに保持し、スコア送信時にIzu Scorelogへ送っています。
狙いは、弱点分析や地力推定。「あなたは縦連が苦手」みたいなことを、ゆくゆくはデータから言えるようにしたいわけです。
プラグインの動的検出は ir/IRConnectionManager、IRへ送るデータ構造は ir/IRChartData。
特徴量の設計は docs/notes-radar-subdivision-design.md にまとめてあります。
Izu Scorelogの本体(サーバ側)はまた別のリポジトリで、このフォークに入っているのは「検出・特徴量算出・送信」までです。
開発の裏側 — AIマルチエージェント体制
最後に、技術者向けの「オチ」を一つ。
実はこのフォーク、機能だけでなく開発プロセスごと魔改造しています。
開発はAIエージェント3体の分業制で回しています。
- Claude — 設計・仕様策定・レビューの司令塔
- Codex — 非UIコードの実装担当
- Gemini / AGY — UI設計・UI実装担当
これに加えて、課題管理は自前の Redmine、コード管理は GitLab(どちらもセルフホスト)。
チケットを切って、設計して、実装をエージェントに振って、レビューして、テストを回してマージ……という流れを、人間(私)が舵を取りつつAIに手を動かしてもらう形で進めています。
コミットログを見ると refs #NNN でチケットに紐づいていて、わりと「ちゃんとした開発」っぽい見た目になっているはずです。
一人の趣味プロジェクトなのに開発体制だけは一丁前、というのが我ながら面白いところです。
まとめ
最初は「自分が一番気持ちよく遊べるbeatorajaにしたい」くらいの気持ちで始めた改造でしたが、気づけば配信・育成・AI検索・独自IRまで入った大ごとになっていました。
特にVRMアバターと自然文検索は、作っていてかなり楽しかったところです。
まだ入れたいものは色々あるので、思いついたらまた増改築していこうと思います。
関連記事
次に読む
シリーズ一覧
すべて見るこのシリーズ情報は未設定です。シリーズ一覧ページから他の連載を探せます。