はじめに
「耳コピが大変だからAIに任せたい」
そんな動機から、音声ファイル(MP3・WAV)をMIDIに変換するWebアプリを作りました。
単純に見えてライブラリ選定で何度もつまずいたので、その経緯をまとめます。
技術スタック(最終構成)
- フロントエンド:React(Vite)
- バックエンド:FastAPI(Python 3.11)
- 音源分離:Demucs(Meta)
- MIDI変換:Basic Pitch(Spotify)
- テンポ・キー検出:librosa
アプリの概要
完成したアプリは2つのページで構成されています。
MIDI変換ページ
- MP3/WAVをアップロード
- 分離パターンを選択(シンプル・標準・詳細)
- 変換開始 →
combined.midをダウンロード
テンポ・キー解析ページ
- 音声ファイルをアップロードするだけでテンポ(BPM)とキーを素早く表示
ライブラリ選定の試行錯誤
MIDI変換:librosa.pyin → Basic Pitch
最初に試したのは librosa の pyin 関数によるピッチ検出です。
f0, voiced_flag, _ = librosa.pyin(
audio,
fmin=librosa.note_to_hz("C2"),
fmax=librosa.note_to_hz("C7"),
sr=sr,
)
結果:使えなかった
pyin は単音楽器(ボーカルや単音フルートなど)向けのアルゴリズムです。
バンドやオーケストラのような複数の音が重なる楽曲には対応しておらず、出力されたMIDIは「曲としてみなせないレベル」でした。
次に採用したのが Basic Pitch(Spotify製)です。
Basic Pitchはニューラルネットワークベースのピッチ検出ライブラリで、複音(和音)に対応しています。
ピアノやギターのコードも検出できるのが大きな強みです。
from basic_pitch.inference import predict
_, midi_data, _ = predict(
wav_path,
onset_threshold=0.4, # 音符の検出感度
frame_threshold=0.3, # 短い音も拾う
minimum_note_length=60, # ノイズ除去(ms)
melodia_trick=True, # メロディ強調フィルター
)
midi_data.write(output_path)
結果:大幅に改善
単音しか拾えなかった pyin と比べ、和音・複音が正しく検出されるようになりました。
完璧ではありませんが、曲として認識できるレベルのMIDIが出力されます。
Python 3.13 vs 3.11 の壁
Basic Pitchを導入しようとしたところ、Python 3.13では動作しないという問題に直面しました。
ERROR: No matching distribution found for tensorflow<2.15.1,>=2.4.1
Basic PitchはTensorFlowに依存しており、TensorFlow 2.15以下はPython 3.11までしか対応していません。
Python 3.13では pkg_resources の仕様変更により、ビルド自体が失敗します。
解決策:Python 3.11の仮想環境を別途作成
# Python 3.11をインストール後
py -3.11 -m venv venv311
venv311\Scripts\pip install basic-pitch
既存のPython 3.13環境には影響せず、FastAPIやDemucsも3.11環境に移行することで解決しました。
音源分離:Demucs の導入
バンドサウンドをそのままBasic Pitchにかけると、複数の楽器音が混在して精度が落ちます。
そこで Demucs(Meta製)で楽器ごとに音源を分離してからMIDI変換する2段構成にしました。
from demucs.pretrained import get_model
from demucs.apply import apply_model
model = get_model("htdemucs")
model.to("cuda") # GPU使用
wav_tensor = torch.tensor(audio).unsqueeze(0).to("cuda")
with torch.no_grad():
sources = apply_model(model, wav_tensor)[0]
# sources: [drums, bass, other, vocals]
使用モデルと分離パターン
| モデル | 分離パート |
|---|---|
| htdemucs | drums, bass, other, vocals |
| htdemucs_6s | drums, bass, guitar, piano, other, vocals |
ドラムはMIDI変換に向かないため全パターンで除外し、残りのパートをそれぞれBasic Pitchにかけて1つのMIDIに統合しています。
combined_midi = pretty_midi.PrettyMIDI()
for stem in ["vocals", "bass", "other"]:
# パートごとにWAVを保存してBasic Pitchで変換
_, midi_data, _ = predict(wav_path, ...)
for instrument in midi_data.instruments:
instrument.program = INSTRUMENT_PROGRAMS[stem]
combined_midi.instruments.append(instrument)
combined_midi.write("combined.mid")
テンポ・キー検出:librosa
MIDI変換とは独立した解析機能として、librosa でテンポとキーを検出しています。
こちらは単純な統計処理なので精度・速度ともに問題ありません。
# テンポ検出
tempo, _ = librosa.beat.beat_track(y=audio, sr=sr)
tempo_val = float(np.atleast_1d(tempo)[0]) # 3.13対応のため atleast_1d を使用
# キー検出(クロマグラム)
chroma = librosa.feature.chroma_cqt(y=audio, sr=sr)
key_idx = int(np.argmax(chroma.mean(axis=1)))
key = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"][key_idx]
注意:librosa 0.11以降、
beat_trackの戻り値がスカラーではなく配列になっています。
float(tempo)ではなくfloat(np.atleast_1d(tempo)[0])とする必要があります。
ライブラリ比較まとめ
| 用途 | 最初に試したもの | 問題点 | 採用したもの |
|---|---|---|---|
| MIDI変換 | librosa(pyin) | 単音のみ対応・複音に弱い | Basic Pitch(Spotify) |
| 音源分離 | なし(直接変換) | 複数楽器が混在して精度低下 | Demucs(Meta) |
| テンポ・キー検出 | librosa | 問題なし | librosa(継続使用) |
つまずきポイントまとめ
- librosa.pyinは単音専用:バンドサウンドには使えない
- Basic PitchはPython 3.13非対応:3.11の仮想環境を別途作成する必要がある
-
librosa 0.11のbeat_track戻り値変更:
atleast_1dで対処 - 音源を混合してからBasic Pitchにかけると精度が落ちる:パートごとに個別変換が正解
おわりに
音声→MIDI変換は「やってみると思ったより難しい」領域でした。
特にBasic PitchのPython 3.13非対応は盲点で、仮想環境を分けることで解決しました。
精度はまだ完璧ではありませんが、Basic Pitch + Demucsの組み合わせで実用的なレベルには達しています。
同じことをやろうとしている方の参考になれば幸いです。
