前回までの記事では、ACE-StepとComfyUI APIを利用して、AIによる音楽生成の自動化を試してきました。
今回はその続きとして、Pythonから直接アクセスできる点を活かし、生成された音楽ファイルを自動評価し、選別する「ガチャ」の構築を試します。
バイブコーディングで評価軸を設定する
生成AIには「評価」そのものを完全に任せることはできませんが、評価軸の設計を相談する相手としては十分に活用できます。
例えば、次のように指示します。
ACE-Stepで生成した音楽のガチャを自動化したい。評価軸を定義してください。
すると、以下のような評価項目が提案されます。
評価軸
- 実時間長さ: duration
生成音声の実測秒数。期待長さとの差をスコア化。
- RMS(音量): rms
波形の二乗平均平方根。正規化してスコアを算出。
- 無音比率: silence_ratio
一定の閾値以下のサンプルが占める割合。
基本スコア: basic_score
長さ・RMS・無音比率を重み付きで合成。
- オンキー比率: on_key_ratio
推定した調のメジャースケールに、ピッチがどれだけ含まれているか。
- ピッチ連続性: pitch_continuity
半音単位の跳躍量から算出した滑らかさ。
- クロマ相関: chroma_corr
クロマ特徴量とメジャープロファイルとの相関。
- スペクトル平坦度: spectral_flatness
ノイズ成分の多さを示す指標(逆指標として使用)。
メロディックスコア: melodic_score
メロディ関連指標の重み付き合成。
総合スコア: score
基本スコアとメロディスコアの合成。
実際には、これらに加えて計算式や補足説明も提示されますが、本記事では要点のみを抜粋しています。
専門家でなくても、こうした評価軸を一緒に考えられる点は、生成AIの大きな利点です。
一方で、評価基準そのものがブラックボックス化するという側面もあり、その妥当性を判断するのは人間の役割になります。
コード例
以下は、生成AIが出力したコードから、評価処理の中核部分を抜粋したサンプルです。
出力された wav ファイルのパスと、想定している生成時間(秒)を受け取り、各種スコアを算出します。
import numpy as np
import soundfile as sf
import librosa
def eval_audio(path: str, expected_sec: int):
y, sr = sf.read(path)
if y.ndim > 1:
y = np.mean(y, axis=1)
y = y.astype(np.float32)
duration = len(y) / float(sr)
rms = np.sqrt(np.mean(np.square(y)))
thresh = 1e-4
silent_ratio = float(np.mean(np.abs(y) < thresh))
duration_score = max(0.0, 1.0 - abs(duration - expected_sec) / expected_sec)
rms_norm = (rms - 1e-4) / (0.05 - 1e-4)
rms_score = float(np.clip(rms_norm, 0.0, 1.0))
silence_score = 1.0 - float(np.clip(silent_ratio, 0.0, 1.0))
basic_score = 0.5 * duration_score + 0.3 * rms_score + 0.2 * silence_score
try:
f0, _, _ = librosa.pyin(
y,
fmin=librosa.note_to_hz('C2'),
fmax=librosa.note_to_hz('C6'),
sr=sr
)
voiced = ~np.isnan(f0)
if np.sum(voiced) > 4:
midi = librosa.hz_to_midi(f0[voiced])
jumps = np.abs(np.diff(midi))
pitch_continuity = 1.0 - np.clip(np.median(jumps) / 3.0, 0.0, 1.0)
chroma = librosa.feature.chroma_stft(y=y, sr=sr)
chroma_mean = np.mean(chroma, axis=1)
major_profile = np.array([6.35,2.23,3.48,2.33,4.38,4.09,2.52,5.19,2.39,3.66,2.29,2.88])
major_profile /= np.sum(major_profile)
corr = [
np.corrcoef(chroma_mean, np.roll(major_profile, r))[0,1]
for r in range(12)
]
chroma_corr = float(np.clip(np.max(corr), 0.0, 1.0))
else:
pitch_continuity = 0.0
chroma_corr = 0.0
flatness = float(np.mean(librosa.feature.spectral_flatness(y=y)))
except Exception:
pitch_continuity = 0.0
chroma_corr = 0.0
flatness = 0.5
melodic_score = (
0.5 * pitch_continuity +
0.3 * chroma_corr +
0.2 * (1.0 - np.clip(flatness, 0.0, 1.0))
)
score = 0.6 * basic_score + 0.4 * melodic_score
return score
コードの妥当性を厳密に検証したものではありませんが、即座に使える点が重要です。
最後は人間が判断する
実際に運用してみると、プロンプトや曲調によって評価指標の向き・不向きがあることが分かります。
- 自然音やノイズが多い曲では、RMSや無音比率が大きくぶれやすい
- メロディが曖昧な曲では、ピッチ関連のスコアが低く出がち
生成AIに結果をフィードバックとして与え、指示を追加することで一定の調整は可能ですが、万能ではありません。
試した範囲では、BGM用途のようにボーカルが入らないケースでは、比較的安定して機能します。
実用上は、次のような使い分けが現実的です。
- 時間が限られている場合は、上位スコアの曲だけをピックアップする
- 品質を重視する場合は、しきい値を決めて低スコアのものを除外する
生成AIにつきものの、不自然な曲構成やリズムの破綻まで完全に検出するのは難しく、やはり「ガチャ」の要素が残ります。コードによる機械的な判断は、あくまで補助的な要素と考えたほうが良いでしょう。
ローカルでの生成にこだわらないのであれば、Sunoのような高品質な有料サービスを使うのも現実的な選択肢です。時短だけでなく、さまざまな良質なサンプルに触れられなど、副次的にも良い効果が得られます。
いずれにしても、最終的な判断は人間が行う必要がありそうです。
次回は、この「最終判断」をどこまで効率化できるかについて考えていきます。




