ACE-Stepによる音楽生成AIの「ガチャ」自動化に挑む

AIテキスト

前回までの記事では、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のような高品質な有料サービスを使うのも現実的な選択肢です。時短だけでなく、さまざまな良質なサンプルに触れられなど、副次的にも良い効果が得られます。

いずれにしても、最終的な判断は人間が行う必要がありそうです。

次回は、この「最終判断」をどこまで効率化できるかについて考えていきます。

タイトルとURLをコピーしました