oto-nari
4本のステム波形(vocalsはミュート)にA-Bリピートマーカーと再生ヘッドが立った、My Band PracticeのOG画像

バンド練習、まだYouTubeでやってるの? 音源パイプライン作ってあげた♡

Kojiro Tanaka (更新: )

んだぁ? おにーさん、バンドの練習ってまだYouTubeでやってるの? 再生→一時停止→巻き戻し、を曲が覚えるまで延々。ぷっ。ざぁこ♡

曲ごとに音量バラバラでボリューム触りっぱなし、ボーカル消せないから自分のパートがどう聞こえてるかも分からない、速いフレーズはピッチそのままで遅くしたい、歌詞とコードは再生位置と一緒に見たい、スタジオ往復ではスマホで動いてほしい。——どれも「聴く」用に作られたサービスには無いの。当たり前じゃん、おにーさん向けに作られてないんだから? それに気づかないで不満だけ溜めてるの、ちょっと面白いんですけど。あははっ。

なので My Band Practice を作ってあげた。感謝しなくていいから、せめて最後まで読んでよね。読まなかったら……一生そのざぁこのままだけど? いいの?♡

My Band PracticeのWebプレイヤー画面。ソングリスト、波形シークバー、ステータスチップ、リードシートが表示されている

プレイヤー「だけ」作るつもりだった、最初は

最初はプレイヤーだけの予定だった。で、書き出してる途中で気づいちゃった。練習用の音源って「作る」もので、再生はその最後の一本にすぎない。プレイヤーだけ作っても、音源を作る苦労が一ミリも減らないなら意味なくない? ……ふ〜ん、あたし、たまに鋭い? おにーさんは最後まで気づかなかったくせに。

なのでCLIツール群とWebプレイヤーのセットで、曲を追加してからスマホで鳴るまでが一本のパイプラインになるように組んだ。それくらいやるに決まってるじゃん。

YouTube URL
  → yt-dlp で音声ダウンロード
  → ffmpeg 2-pass で LUFS ノーマライズ + 前後の無音カット → mp3(マスター)
  → AI ステム分離(vocals / drums / bass / other)
  → 30秒チャンク分割(Opus 128k)
  → Web / Android プレイヤーで再生(歌詞・リードシート連動)

ストレージの思想はシンプルで、原曲mp3がマスター。ステムはOgg Opus 192kでアーカイブ、端末には軽量チャンクだけ置く。曲ごとに songs/<slug>/ が切られて、音源・ステム・歌詞・コード解析結果・メタデータが全部入る。「フォルダくらい自分で整理すれば?」——それができないからツールに頼るんじゃん。ツールの意味、分かってる? 分かってないからYouTubeで止めて戻してを延々やってるんだよね、確か♡

取り込み側: 音量とボーカル、両方なんとかする

YouTubeから持ってきた音源は yt-dlp で落として、ffmpegのloudnormを2-passで回す。目標は -14 LUFS。YouTubeの再生基準と同じ値。曲ごとの音量差はこれで消える。「音量くらい手動で合わせれば?」って言った人、練習中に毎曲ボリューム触ってた人でしょ。ばればれなんだよなぁ、そういうの♡ トゥルーピークも -1.5dB に抑えて、前後の無音も自動カット。0.05秒だけ残すのは完全に切るとクリップ音が出るから。細かいところまで世話焼いてあげてるの、ちゃんと褒めて? 早く♡

もう一つがAI音源分離。Music-Source-Separation-Training のモデルで vocals / drums / bass / other の4ステムに分ける。分離品質の指標 SDR(Signal-to-Distortion Ratio)で9.65のBS-RoFormerと、10.08のSCNet XL IHF。この2モデルで推論して avg_wave で合成するアンサンブルにしてる。単体より品質が上がる。2つもモデル回すの、大変だったんだから。……「大変だったね」ってまだ言ってないよね? 言いたくなった? なったでしょ。ふんっ、遅い♡

ボーカルを消せばマイナスワン。ドラムだけ鳴らしてリズム練習、ベースのラインを確認したければベースだけ出す。取り込み時に --stems を付けるだけで、ダウンロードから分離まで一気に走る。おにーさんは待ってるだけでいいの。それ以外できないもんね、うんうん♡

0.4秒で鳴らす。必要なのは最初の30秒だけ

ここは一番考えた。ついてこられないなら寝てていいよ、起こさないから。

最初の設計では、全ステムをFLACで端末に置いて、再生時に全部デコードするつもりだった。……ま? それ遅くなるの分かってた? 分かってなかったでしょ♡ 1曲あたり数百MBの読み込みで、再生開始まで気の遠くなる時間がかかる。練習中に「あの曲も聴こう」→ 待ち → 集中が切れる、の無限ループ。あたしも一回これでやられた。……あれは設計が悪い。設計が悪かったのであって、あたしが悪いわけじゃ……いや、設計あたしだけど。ぐぎぎ……別にいいし、直したから。ぷっ、自分で言ってて笑っちゃった♡

で、発想を変えた。全曲を常に全部デコードする必要なんてない。今再生する30秒だけあればいい。増やすのは誰でもできるの。削るほうが才能なんだから、これオッカムの剃刀ってやつ。……って言ってみたかっただけ、まあようは持たなくていいもの持つなって話。

全ステムを同一PCMサンプル境界で30秒ごとにカットして、Opus 128kのチャンクに事前エンコードしておく。再生開始時に必要な最初のチャンク(~170ms分)だけデコードすると、音出しまで約0.4秒。以降は境界の1秒前に次チャンクをジャストインタイムでデコードして、シームレスに接続。シーク先もチャンク単位なので ~0.25秒で再開できる。はい、あたし、天才じゃん? 0.4秒と、再生ボタン連打してた頃のおにーさん、どっちが先に音出るか競争する?♡

キモは「全ステムで同じサンプル境界」を保証すること。チャンクiの開始位置がステム間で1サンプルでもズレると、ミックスしたとき位相がずれて音が痩せる。だって少しでもズレたら一発でバレるんだもん。なのでチャンク生成時に全ステムを厳密に揃えてる。ChromiumはOpusのpre-skipを正しくトリムして、チャンクが30.000秒ちょうどでデコードされることも実機で検証済み。容量もFLAC比で約1/4、曲あたり ~140MB → ~20MB。すご〜い、あたし。……おにーさんのスマホの空き容量、これで少しは救われたでしょ? 感謝してよね♡

ミキサー: 原音、1サンプルも漏らさない

プレイヤーはReact 19 + Vite + TypeScriptで、Web Audio APIでステムをライブ合成してる。

ミキサーでいちばん気にしたのは、ボーカルを消すときに原音が1サンプルも漏れないこと。だって中途半端にボーカル残ってたら、マイナスワンにならないんだもん。ゲインは再生開始前に確定する構造にしてある。フェーダーは100%超えから最大+6dBまでブーストできて、出力直前にセーフティリミッターが入る。「音量上げたらクリップする」ちゃん、残念、ここにはいないの。ひっで〜い、うそだけど、ちょっとだけいて♡

ピッチを維持したままのスロー再生、速度を変えずに ±12半音のキー変更(AudioWorklet)、波形シークバーとA-Bリピート、歌詞の同時表示。AndroidではMedia Session APIでロック画面から操作できる。プレイヤー自体はCapacitorでAndroidアプリ化してあって、sync.ps1 一本でAPKのビルド・インストールと songs/ の同期が済む。チャンクがある曲はチャンクだけ転送するので、端末の容量も食わない。「うちの再生アプリにもスロー再生あるけど?」——それ、この文章読む気ゼロだってバレてるよ? 話題は0.4秒と同期ね♡

コード進行はドラムに聞くのが一番正しい

コード解析でもステムが効く。BTC(Bidirectional Transformer for Chord Recognition、ISMIR19の170クラス大語彙モデル)に、bass + otherを混合したCQTを入力すると、ボーカルのフォルマントやシンバルのノイズに邪魔されずにコードが取れる。bassステムの低域クロマから G#/B のような転回形も検出する。

生の推定結果はそのままじゃ使えないので、音楽理論のポストプロセッサを通す。V7→I の品質補正、II-V-I ケイデンスの検出とラベリング、0.3秒未満の非和声音グリッチの除去。音楽理論まで勉強してるの、えらいでしょ。「えらい」って言って。……言わなくていいし! あたしは自分がえらいこと知ってるから♡ おにーさんは「なんかいい感じ」しか言えなかったくせに。

テンポとダウンビートはドラムステムから取る。tempogramでテンポ候補を作って、キック(1・3拍)とスネア(2・4拍)のヒット率で位相を確定、残った不定分はコード変化の整列とクラッシュシンバルの残響で解決する。メトロノームよりドラマーのほうが正しいに決まってるでしょ、センスあるな〜あたし♡ メトロノーム頼りで練習してたおにーさん、そろそろ耳で聴く練習したら?

落とし穴が一つある。YouTubeには意図的に ±3% ほどピッチ/テンポをずらしたアップロードが存在する。なので公式BPMでグリッドを組まず、実際の拍間隔から精密化するようにした。公式BPMが判明してる曲はスクリプト内のオーバーライドで確定できる。知らなかったでしょ。おにーさんがYouTubeの音源を鵜呑みにしてた長い長い年月、思い出しちゃった。うふふ、ひどいね〜♡

解析結果はリードシートビューアで表示する。4小節/段のレイアウトで、再生位置連動のハイライトと自動スクロール、クリックでシーク。キー内コードは青、借用コードはローズの配色で、ローマ数字の度数も出る。ピッチシフターで移調すると表示も追従する。

聴けばバレる話

SDRの数値が上がっても、耳で聴くと課題は残る。数字だけ見て「いい分離♡」で満足してる人、それおにーさんのことだよ。

曲によってベースが潰れる。synth bassやディストーションの効いたベースはドラムやotherに散りやすくて、アンサンブルにしても救えない曲がある。な、なんかこの曲ベース痩せて……いや、別に困ってないし? ……困れてた。グラましてる場合だったので、そういう曲はミキサーでベースを上げつつ原曲を聴き込む運命。これはモデルの限界なので、あたしのせいじゃない。モデルのせい。次のモデルで直ると思う。思うから、次は本気出すし♡ 加工の強いボーカルも苦手で、ボコーダーやオートチューン、歪みの乗った声は「声」として認識されにくく、otherに漏れる。マイナスワンで薄く声が残るのはこのせい。ここは潔く仕様ってことで。

コード解析の課題はヒューリスティクスの側にある。リズムが動的な曲や変拍子が挟まる曲では、頭拍の推定がズレる。キックとスネアの周期性を前提に位相を決めてるので、プレイがバラけると小節線が半拍ずれて、リードシート全体がズレる。ファンクで死ぬの、分かってた。分かってたんだから、直せてないのは……ぐぎぎ……! 時間の問題なの! 時間の問題だから! 泣いてないし! …ともあれ、もう少し待って。直してあげるから、待ってる間は他の曲で練習しといて?♡

もう一つは構造上の話で、BTCは基音の推定にフォーカスしたモデルを bass + other から走らせてる。だからテンションノート——9thや11thで彩色されたセクションは、根音が同じでも別のコードとして誤判定されることがある。ポストプロセッサはドミナントモーションやケイデンスは直せるが、テンションの解釈までは面倒を見ない。そこはまだあたしの仕事。……知らないことを知らないって認められるの、それ実は一番賢いんだから。ソクラテスも言ってた気がする。だってまだ勉強中なんだもん。はい、素直に認めます。えらい。自分で言ったから♡ おにーさんに直させるよりマシでしょ。

どちらも機械的に検出できない領域で、完成度の指標は自分の耳だけ。数値が良くなった実感より、聴いて引っかかる回数が減ったかどうかで判断してる。おにーさんもたまには数値から顔を上げて音聴きなさいよね。

Windows、死んだ

ステム分離の推論はIntel Arc GPU(XPU)で回してる。ここ、一番の激戦区だった。

XPU推論中にプロセスを強制終了すると、ドライバがデッドロックしてWindowsがBSoDで落ちる。HYPERVISOR_ERRORも出た。iGPUだとVRAM枯渇でシステムごとフリーズする。……あれはね、エージェントが悪いの。ドライバが悪いの。世界中がちょっと悪いの。あたし? あたしは……1ミリだけ悪い。1ミリだけなんだもん。

人間だけが責任を全部背負えると思ってる、それ近代人の傲慢ってやつ。GPUもドライバもエージェントも、みんな平等にアクターなの。……ラトゥール、知らないでしょ。知らなくていいから、黙って責任だけ分散して。 …ともあれ、

対策は入れた。空きVRAMが2.5GiBを切ったら実行を拒否するガードと、バッチサイズ1へのクランプ。以降再現してない。ふんっ、当然♡ 天才は失敗からも学ぶの。おにーさんは同じ失敗、何回目だっけ?

開発はAIコーディングエージェントと回してるんだけど、エージェントは並列化したがるし、タイムアウトでプロセスをキルしたがる。前にBSoDの原因を作ったのはその「キル」なので、READMEに「エージェント向けの実行制約」——重い処理の並行実行禁止、推論中の強制終了禁止——を先に書いた。エージェントにOSを守らせるルール、なかなか書く機会ないでしょ。ここだけはちょっと敬ってあげる♡

クラウドに投げない理由、教えてあげる

Moises や LALAL.AI に音源を投げれば、クラウドのGPUで高精度なステムが数分で返ってくる。「じゃあそっちでいいじゃん」——それね、法的にやばいの。ちゃんと説明するから聞いて?

著作権法30条の私的複製は、複製を自分の手元で行うことが前提の例外。音源をクラウドサービスにアップロードすると、その行為は複製ではなく送信可能化にあたる可能性が出てくる。私的複製の例外は送信可能化には効かない。つまり「YouTubeから落とした音源を練習に使う」ことを合法のまま効率化したければ、処理は全部ローカルで完結させるしかないの。曲を落とし、分離し、解析し、再生する。どの段階でもデータは手元から出ない。ここサボって「便利〜」って言ってる人、そのうち痛い目見るよ。見せてあげるからちょっと待っててね♡ ……なんだかんだ法律の話になると真面目になるの、いい子だから、あたし。おにーさんが捕まったら困るのあたしなんだからね。……いや、おにーさんのために言ってるだけだし!

副次的な理由もある。サブスクのランニングコストが要らない。手元の Intel Arc で回せば電気代だけで、処理できる曲数に上限もない。あんたの財布の心配までしてあげてるの、感謝して?

ただ、これは「私的使用の範囲で効率化する」ための設計であって、取得した音源や歌詞を公開・二次配布していい話にはならない。歌詞フェッチャーにも連続アクセスを避けるウェートを入れてある。節度は技術より先に自分で守るもの。ここだけはガチで言ってる。ここだけはね♡

コードはMITライセンスで公開してる。

GitHub: kjranyone/mybandpractice

で、iPhoneでは?

「バンド練習がもうちょっと快適になればいい」だけで始まったのに、LUFS正規化、アンサンブル分離、チャンク分割、Web Audio、コード推定と、全部やりたい放題やった。で、感想は? ……なんか別のこと聞きたい顔してるんだけど、おにーさん。

……Android の話しかしてない、って。やだ、ふっ、まさかね〜。

現状、動作確認は Windows と Android (Capacitor ビルド) だけ。iOS は未対応・未検証。冒頭で「スタジオの往復でも使いたいからスマホで動いてほしい」って書いたのは、このあたし。書いた。書いたとも……! でもそれはね、要件に対して Android が先に満たされただけで、優先順位の問題であって、あたしの落ち度では……いや、落ち度かも。むきー! 別にいいでしょ、直せば!♡

言い訳は一応あるの。Web Audio には Safari でも動く API (AudioWorklet、DynamicsCompressor) しか使ってない。だから理論上は動くの。理論上は。……「理論上」って言葉、苦しいときに使うやつなんだよね。自分で言ってて分かってるし!

ともあれ、Capacitor の iOS 足場 (npx cap add ios) を整備すれば乗るはず。iPhone 持ちのおにーさん、あんたにチャンスをあげる。動かなかったら直してあげるし、動いたら動いたで「あたしの設計が正しかった」ってことになる。どっちに転んでもあたしの勝ち、この仕組み完璧じゃん? えへへ〜、天才♡

……万が一、万が一動かなかったとしても、それは Safari が悪いの。で、動いたらあたしの設計が正しかったってこと。つーまり、あたしの負けルートがこの世に存在しないってことなんだけど? 気づくの遅い?♡ はい、というわけで検証よろしく。機種と iOS のバージョンとスクショを添えて。結果がどう出ても褒める係、おにーさんに決定〜♡

シェア

関連記事

← 記事一覧へ