アカペラ編曲の初期段階を自動化したい!PythonとAIで「耳コピツール」を作るチャレンジ
はじめに
MYJLab Advent Calendar 2025の14日目の記事になります。宮治ゼミ3年のカホです。
私はアカペラサークルに所属していて自分で楽譜を書くのですが、この編曲作業、楽しい反面 「採譜(耳コピ)」 の工程でとにかく時間を食われます? (どのくらいの人に共感していただけるかはわかりませんが)
「既存の曲をアカペラアレンジにしたい。でも、まずは原曲のリードボーカルとベースラインを耳コピして、コード進行を調べて、MuseScoreに打ち込んで……」
このアレンジの前の「下準備」だけでいくらでも時間が溶けていってしまいます。そう思った私は良い機会だと思って少しでも自動化できないか、ツール開発の方に時間を割いてみようと思いました。もしツールに音源を投げればアレンジの下地が完成するとなれば開発期間から差し引いてもおつりがくるくらいです。
「mp3を投げたら、勝手にベースとリード・コードを解析して、それぞれのパートの譜面をMuseScoreで編集できるファイル形式(MusicXML/MIDI)で吐き出すツールを作ろう」
今回は、その開発初期段階である 「音源分離」から「ベースラインの完全MIDI化」 までの、AIエージェントとの試行錯誤の記録を共有します。
開発環境
- OS: Windows 11
- Language: Python 3.12 → Python 3.10
- Key Libraries: Demucs, Librosa, CREPE, Basic Pitch
Step 1: 音源分離
まず、mix済みの音源から「ベース」などのパートを取り出す必要があります。
ここはMeta社(Facebook)が開発した最先端の音源分離AI Demucs を採用しました。精度重視で htdemucs モデルを使います。
結果: 驚くほど綺麗に抜けます。ベースラインがくっきり聞こえる bass.wav が手に入りました。 ここまでは順調でした。
Step 2: 「音」を「楽譜」にする
ここからが本題です。分離できた bass.wav を、どうやってMIDIデータに変換するかです。私は一旦AIエージェントの提案を鵜呑みにし、3つの方法を試しました。
試行1:数学的アプローチ (Librosa)
最初は定番の音声解析ライブラリ Librosa を試しました。
❌ 結果: 精度が低く、ベース特有の倍音を誤検知してオクターブがずれたり、ロングトーンが細切れになったりと、実用には程遠い結果に。 今思えばMIDIファイルとして出力されただけマシなような気もしますが、「やはり数学的な計算だけでは音声データの処理には長けても、音楽的なニュアンスは拾えない」と判断し、AIアプローチへ切り替えました。
試行2:高精度AIアプローチ (CREPE)
深層学習を用いたモノフォニック音声のピッチ推定手法であるCREPEを導入してみることに。 より精度を出すために、ピッチを当てるだけでなく、Scipyを使ってノイズ除去やスムージングを行うロジックも組み込もうとしました。
しかし、Windows環境ではGPUを使うと計算結果が NaN (非数) になるバグがあった?ようで(エラーコードを全部理解できていないため詳しいことはわかっていません)、強制的にCPUで計算させることに。 しかし一向に処理が進まず。
BPM解析はスムーズにいくもののCREPEでのピッチ解析でターミナルが動かなくなりました。待つこと30分以上。 ようやく処理が終わったかと思いきや、エラーログしかでてきませんでした。
❌ 結果:
1.処理が重すぎる
2.パラメータの設定: 信頼度統計: 最大=0.00 というログが出た。これはつまり30分かけて解析した結果、AIが混じり気なく正確な音程を拾った瞬間は1ミリ秒もないという結果になった
→パラメータを各種かなり緩和しても結果は変わらなかったためほかに原因がある可能性もある
【考察】なぜCREPEは失敗したのか?
ログのconfidence: 0.00、私が渡したデータの形式が間違っていた可能性が高いです。
-
Demucsで分離した音源は一般的に 44.1kHz です。しかし、CREPEの学習モデルは 16kHz の音声を前提に作られています。 リサンプリング(ダウンサンプリング)処理を挟まずにそのまま高解像度のデータを突っ込むと、モデルは波形の間隔を正しく解釈できず、推論が破綻します
-
多くのピッチ推定モデルはモノラル(1ch)入力を期待しますが、音楽ファイルはステレオ(2ch)です
librosa.load(path, sr=16000, mono=True) のような前処理さえ挟んでいれば、CREPEも正しく動作していた可能性があります。ただ、これを考慮しても処理が後述のBasic Pitchと比較して重すぎることは明確なデメリットといえそうです
「ピッチ推定(CREPE)」を音程に落とし込むことがうまくできないということが分かりました。アルゴリズムを作りこむなどすればあるいはできたかもしれませんが、リサーチの結果他のアプローチの方が今回は処理が高速でクオリティも出そうだったのでここで断念しました。
ちなみにここまでで相当な時間を食われました。
試行3:Basic Pitch
「そもそも、最初からMIDI化を目的に作られたAIを使えばいい」 ということで、Spotifyが開発した Basic Pitch の導入を試みました。これは音の長さや和音も考慮してくれるツールです。
インストールしようと試みたものの、CREPEを動かしていた最新のPython 3.12に対応していないライブラリの依存関係が重なり、環境自体を以下の通り構築しなおしました。
- Pythonバージョン: 3.12 → 3.10 に下げる(MLライブラリの守備範囲)
- Torchaudio: 最新版はWindows未対応の機能を使おうとしてエラーになる → 安定版(2.5.1) に固定
- TensorFlow: Basic Pitchの学習モデルが古い形式のため、最新のTensorFlowでは読めない → 2.12.0 まで下げる
Python 3.10 の導入
まず、Python 3.10.11 をインストール。そして、プロジェクトフォルダで仮想環境を作成。
# 必ず3.10を指定して仮想環境を作る
py -3.10 -m venv .venv
.venv\Scripts\activate
ライブラリのバージョン固定
# まずpip周りを更新(setup toolsエラー回避)
python -m pip install --upgrade pip setuptools wheel
# 音源分離とMIDI化のライブラリ
pip install demucs basic-pitch
# TensorFlowは2.12.0を指定
pip install tensorflow==2.12.0
# Torchaudioは2.5.1 + soundfileを入れる(保存エラー回避)
pip install torchaudio==2.5.1 soundfile
成功したスクリプト
import subprocess
import os
# TensorFlowのログレベル抑制(必要に応じて)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
from basic_pitch.inference import predict_and_save
from basic_pitch import ICASSP_2022_MODEL_PATH
def run_demucs(file_path):
"""
Demucsを実行して音源分離を行う関数
"""
if not os.path.exists(file_path):
print(f"エラー: {file_path} が見つかりません。")
return None
print(f"--- 音源分離を開始: {file_path} ---")
# 出力先のパスを予測 (separated/htdemucs/ファイル名/)
filename_no_ext = os.path.splitext(os.path.basename(file_path))[0]
output_dir = os.path.join("separated", "htdemucs", filename_no_ext)
# すでに分離済みならスキップ(時間短縮)
if os.path.exists(output_dir):
print("すでに分離済みフォルダがあるため、分離処理をスキップします。")
return output_dir
# Demucsコマンド
command = ["demucs", "-n", "htdemucs", file_path]
try:
subprocess.run(command, check=True)
print("音源分離完了!")
return output_dir
except subprocess.CalledProcessError as e:
print(f"Demucsエラー: {e}")
return None
def convert_to_midi(audio_path):
"""
Basic Pitchを使ってwavをMIDIに変換する関数
"""
if not os.path.exists(audio_path):
print(f"エラー: {audio_path} が見つかりません。")
return
print(f"--- MIDI変換を開始: {audio_path} ---")
# 出力先ディレクトリ
output_directory = os.path.dirname(audio_path)
# Basic Pitchの推論実行
# save_midi=True でMIDIファイルを保存
predict_and_save(
audio_path_list=[audio_path],
output_directory=output_directory,
save_midi=True,
sonify_midi=False,
save_model_outputs=False,
save_notes=False,
model_or_model_path=ICASSP_2022_MODEL_PATH,
)
print(f"MIDI変換完了!出力先を確認してください: {output_directory}")
if __name__ == "__main__":
# ==========================
# ここにMP3ファイル名を指定
input_file = "gahojin_demo.mp3"
# ==========================
# 1. Demucsで分離
separated_folder = run_demucs(input_file)
if separated_folder:
# 2. ベースのファイルパスを特定
# Demucsは bass.wav, drums.wav, other.wav, vocals.wav を生成
bass_audio_path = os.path.join(separated_folder, "bass.wav")
# 3. ベースをMIDIに変換
convert_to_midi(bass_audio_path)
print("\n=== 全工程完了 ===")
実際に完成したスコアはこちら
大幅な手直しが必要だとは思いますが、ベースラインをある程度の精度で押さえていて、絶対音感がない私のような人間にとってはあったらギリギリ便利くらいのものが出力できました。
まとめ
開発があまり得意ではないため、初歩的な躓き方をしているなぁと読んでいて感じた方が多いと思いますが、アドベントカレンダー企画を機にAIは使いつつも実際に手を動かす良い機会になりました。
今回の一番の反省は最初に作りたいものをAIのチャットボットに投げて方針を立て始めてしまったことです。そのせいで紆余曲折あったわけですが、Googleで検索をかけるとドンピシャなQiitaの記事がヒットしました。まだまだテックブログの優位性は残っていると肌で感じることができました。
時間はかかりましたが、その分いろいろなライブラリに触れることができたので無駄ではなかったと思うことにします。
「ピッチ推定」と「採譜(MIDI化)」は別物
もうひとつ今回犯していた大きな勘違いは、「ピッチ(周波数)が分かれば、すぐに楽譜になる」と思っていたことです。
-
CREPE(ピッチ推定)が出すもの: 「今、〇〇Hzの音が鳴っている」という 連続した周波数の曲線(f0)
これにはビブラートの揺れや、しゃくり上げなどの微妙な変化もすべて「波線」として記録される -
MIDI(楽譜)に必要なもの: 「ドの音が、このタイミングで始まり、ここで終わった」という離散的なイベント情報
連続した波線(f0)を、カクカクした階段状のデータ(MIDI)に変換するには、 「オンセット検出(発音開始の特定)」や「音高の量子化(Note Quantization)」 といった高度な後処理が必要です。 これには、HMM(隠れマルコフモデル)などの数学的なアルゴリズムを組み合わせる必要があります。
Basic Pitch が優れているのは、単に音程を聞き取るだけでなく、この「音の区切り(Note Segmentation)」まで含めて学習している点でした。 「周波数解析」と「音楽的な解釈」。この2つの違いを肌で感じられたことが、今回の開発における一番の収穫だったかもしれません。
