Skilore

音楽生成 — Suno、Udio、MusicGen

AIによる音楽生成技術の仕組み、主要サービスの比較、プロンプトエンジニアリングを解説する

86 分で読めます42,691 文字

音楽生成 — Suno、Udio、MusicGen

AIによる音楽生成技術の仕組み、主要サービスの比較、プロンプトエンジニアリングを解説する

この章で学ぶこと

  1. AI音楽生成の技術的アーキテクチャ(コーデック言語モデル、拡散モデル)
  2. 主要サービス(Suno、Udio、MusicGen)の特徴と使い分け
  3. 効果的なプロンプトの書き方と音楽生成ワークフロー

前提知識

このガイドを読む前に、以下の知識があると理解が深まります:

  • 基本的なプログラミングの知識
  • 関連する基礎概念の理解

1. AI音楽生成の技術基盤

1.1 音楽生成モデルのアーキテクチャ分類

AI音楽生成の主要アーキテクチャ
==================================================

1. コーデック言語モデル型(MusicGen, MusicLM)
   テキスト → LM → 音声コーデック → 波形
Prompt──→Language──→Codec──→Audio
ModelDecoderWave
└──────────┘   └─────────┘

2. 拡散モデル型(Stable Audio, Riffusion)
   テキスト → 潜在空間で拡散 → デコード → 波形
Prompt──→Diffusion──→VAE──→Audio
(UNet)DecoderWave
3. ハイブリッド型(Suno, Udio)
   テキスト + 歌詞 → 複合モデル → 音楽(ボーカル+伴奏)
Prompt──→Multi-stage──→Full
+ LyricsGenerationSong
└───────────────┘
==================================================

1.2 Encodecによる音声トークン化

# Encodecの概念: 音声をトークン列に変換
 
class EncodecConcept:
    """
    Encodec(Meta): ニューラル音声コーデック
    - 音声波形 → 離散トークン列 → 音声波形
    - 複数のコードブック(残差量子化)
    - 帯域幅に応じた品質制御
    """
 
    def __init__(self):
        self.n_codebooks = 8        # コードブック数
        self.codebook_size = 1024   # 各コードブックの語彙サイズ
        self.frame_rate = 75        # 75 Hz(1秒 = 75フレーム)
 
    def encode(self, audio_waveform):
        """音声 → トークン列"""
        # Step 1: エンコーダ(CNN)で特徴抽出
        features = self.encoder(audio_waveform)
        # Step 2: 残差量子化(RVQ)で離散化
        # 1つ目のコードブック: 粗い表現
        # 2つ目以降: 前の量子化誤差を補正
        codes = self.quantizer(features)  # shape: (n_codebooks, n_frames)
        return codes  # 例: (8, 75) for 1秒の音声
 
    def decode(self, codes):
        """トークン列 → 音声"""
        features = self.dequantizer(codes)
        audio = self.decoder(features)
        return audio
 
# MusicGenでの利用
# テキスト → Transformer LM → Encodecコード → Encodecデコーダ → 音声

1.3 テキスト-音楽アライメントの仕組み

# CLAP (Contrastive Language-Audio Pretraining) による
# テキストと音楽の意味的対応付け
 
class CLAPConcept:
    """
    CLAP: テキストと音声を共通の埋め込み空間にマッピング
    - 画像のCLIPと同様のアーキテクチャ
    - テキストエンコーダ + オーディオエンコーダ
    - 対照学習で同じ意味の組を近づける
    """
 
    def __init__(self):
        self.text_encoder = None  # BERT/RoBERTa ベース
        self.audio_encoder = None  # HTS-AT / HTSAT ベース
        self.embedding_dim = 512
 
    def compute_similarity(self, text: str, audio_path: str) -> float:
        """テキストと音声の類似度を計算"""
        text_embedding = self.text_encoder(text)  # (512,)
        audio_embedding = self.audio_encoder(audio_path)  # (512,)
 
        # コサイン類似度
        similarity = np.dot(text_embedding, audio_embedding) / (
            np.linalg.norm(text_embedding) * np.linalg.norm(audio_embedding)
        )
        return similarity
 
    def rank_generations(self, prompt: str, audio_paths: list) -> list:
        """生成された複数の音楽をプロンプトとの一致度でランキング"""
        scores = []
        for path in audio_paths:
            score = self.compute_similarity(prompt, path)
            scores.append((score, path))
        return sorted(scores, reverse=True)
 
# 使用例: 生成候補のランキング
# clap = CLAPConcept()
# ranked = clap.rank_generations(
#     "upbeat electronic dance music",
#     ["gen_1.wav", "gen_2.wav", "gen_3.wav"]
# )

1.4 条件付き生成の詳細メカニズム

条件付き音楽生成のメカニズム
==================================================

1. Classifier-Free Guidance (CFG)
output = (1 + w) * cond_output
- w * uncond_output
w = CFGスケール(大きいほどプロンプト忠実)
cond_output = プロンプト条件付き出力
uncond_output = 無条件出力
CFGスケールの影響:
   - w = 1.0: 弱い条件付け(多様性高い)
   - w = 3.0: 標準(バランス)
   - w = 5.0+: 強い条件付け(プロンプト忠実だが多様性低い)

2. メロディ条件付き生成
MelodyExtractionAttention
└──────────┘
   * クロマグラム: 12半音のエネルギー分布
   * メロディの輪郭を保持しつつ新しい音楽を生成

3. オーディオ条件付き生成(AudioGen的アプローチ)
AudioEncoderPrompting
* 参照音声のスタイルを引き継いで生成
==================================================

2. 主要サービスの詳細

2.1 MusicGen(Meta)

from audiocraft.models import MusicGen
from audiocraft.data.audio import audio_write
 
# MusicGen モデルのロード
model = MusicGen.get_pretrained("facebook/musicgen-large")
model.set_generation_params(
    duration=30,          # 生成時間(秒)
    top_k=250,           # Top-K サンプリング
    top_p=0.0,           # Top-P サンプリング(0=無効)
    temperature=1.0,     # 温度パラメータ
    cfg_coef=3.0,        # Classifier-Free Guidance 係数
)
 
# テキストからの音楽生成
descriptions = [
    "upbeat electronic dance music with heavy bass and synth leads, 128 BPM",
    "gentle acoustic guitar fingerpicking with soft piano, relaxing ambient",
    "epic orchestral soundtrack with dramatic strings and brass, cinematic",
]
 
wav = model.generate(descriptions)
# wav shape: (3, 1, sample_rate * duration)
 
# 保存
for i, one_wav in enumerate(wav):
    audio_write(
        f"output_{i}",
        one_wav.cpu(),
        model.sample_rate,
        strategy="loudness",
        loudness_compressor=True,
    )
 
# メロディ条件付き生成
import torchaudio
 
melody_waveform, sr = torchaudio.load("melody_reference.wav")
wav = model.generate_with_chroma(
    descriptions=["jazz piano improvisation over the given melody"],
    melody_wavs=melody_waveform,
    melody_sample_rate=sr,
)

2.2 MusicGen の高度な使い方

import torch
from audiocraft.models import MusicGen, MultiBandDiffusion
from audiocraft.data.audio import audio_write
 
class MusicGenAdvanced:
    """MusicGenの高度な活用パターン"""
 
    def __init__(self, model_size="large"):
        """
        model_size options:
        - "small": 300M params, 低VRAM、高速
        - "medium": 1.5B params, バランス
        - "large": 3.3B params, 最高品質
        - "melody": メロディ条件付き対応
        - "stereo-*": ステレオ出力対応
        """
        self.model = MusicGen.get_pretrained(f"facebook/musicgen-{model_size}")
        self.mbd = None  # MultiBandDiffusion(高品質デコーダ)
 
    def generate_with_continuation(
        self,
        prompt: str,
        audio_prefix: str,
        total_duration: float = 60.0,
        overlap: float = 5.0,
    ) -> torch.Tensor:
        """
        音声の続きを生成(長尺対応)
        MusicGenの30秒制限を超えて長い楽曲を生成
        """
        import torchaudio
 
        prefix_wav, sr = torchaudio.load(audio_prefix)
        if sr != self.model.sample_rate:
            prefix_wav = torchaudio.functional.resample(
                prefix_wav, sr, self.model.sample_rate
            )
 
        segments = [prefix_wav]
        generated_duration = prefix_wav.shape[-1] / self.model.sample_rate
 
        while generated_duration < total_duration:
            # 直前のオーバーラップ部分を入力として続きを生成
            overlap_samples = int(overlap * self.model.sample_rate)
            context = segments[-1][:, -overlap_samples:]
 
            self.model.set_generation_params(
                duration=min(30, total_duration - generated_duration + overlap),
                cfg_coef=3.0,
            )
 
            continuation = self.model.generate_continuation(
                prompt=context.unsqueeze(0),
                prompt_sample_rate=self.model.sample_rate,
                descriptions=[prompt],
            )
 
            # オーバーラップ部分をクロスフェードで結合
            new_segment = continuation[0][:, overlap_samples:]
            segments.append(new_segment)
            generated_duration += new_segment.shape[-1] / self.model.sample_rate
 
        return torch.cat(segments, dim=-1)
 
    def generate_variations(
        self,
        prompt: str,
        n_variations: int = 5,
        temperature_range: tuple = (0.7, 1.3),
    ) -> list:
        """
        同じプロンプトから異なるバリエーションを生成
        温度パラメータを変えて多様性を制御
        """
        variations = []
        temps = torch.linspace(
            temperature_range[0], temperature_range[1], n_variations
        )
 
        for temp in temps:
            self.model.set_generation_params(
                duration=15,
                temperature=float(temp),
                top_k=250,
                cfg_coef=3.0,
            )
            wav = self.model.generate([prompt])
            variations.append({
                "audio": wav[0],
                "temperature": float(temp),
            })
 
        return variations
 
    def batch_generate_with_styles(
        self,
        base_theme: str,
        styles: list,
    ) -> dict:
        """
        同じテーマを異なるスタイルで一括生成
        例: "夏の海" を Jazz, Lo-fi, Rock 等で生成
        """
        prompts = [f"{style} music about {base_theme}" for style in styles]
 
        self.model.set_generation_params(duration=30, cfg_coef=3.0)
        wavs = self.model.generate(prompts)
 
        results = {}
        for i, style in enumerate(styles):
            audio_write(
                f"{base_theme}_{style}",
                wavs[i].cpu(),
                self.model.sample_rate,
                strategy="loudness",
            )
            results[style] = wavs[i]
 
        return results
 
# 使用例
gen = MusicGenAdvanced("large")
 
# 長尺生成(60秒)
long_track = gen.generate_with_continuation(
    prompt="ambient electronic with evolving textures",
    audio_prefix="seed_audio.wav",
    total_duration=60.0,
)
 
# 5バリエーション生成
variations = gen.generate_variations(
    prompt="upbeat J-Pop with catchy synth melody",
    n_variations=5,
)

2.3 Suno APIの利用

import requests
import time
 
class SunoClient:
    """Suno AI 音楽生成クライアント(非公式API概念)"""
 
    BASE_URL = "https://api.suno.ai/v1"
 
    def __init__(self, api_key: str):
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }
 
    def generate_song(
        self,
        prompt: str,
        lyrics: str = None,
        style: str = None,
        title: str = None,
        instrumental: bool = False,
    ) -> dict:
        """楽曲を生成"""
        payload = {
            "prompt": prompt,
            "make_instrumental": instrumental,
        }
        if lyrics:
            payload["lyrics"] = lyrics
        if style:
            payload["style"] = style
        if title:
            payload["title"] = title
 
        response = requests.post(
            f"{self.BASE_URL}/generate",
            json=payload,
            headers=self.headers,
        )
        return response.json()
 
    def wait_for_completion(self, task_id: str, timeout: int = 300) -> dict:
        """生成完了を待機"""
        start = time.time()
        while time.time() - start < timeout:
            status = self.check_status(task_id)
            if status["state"] == "completed":
                return status
            elif status["state"] == "failed":
                raise Exception(f"生成失敗: {status.get('error')}")
            time.sleep(5)
        raise TimeoutError("生成タイムアウト")
 
# 使用例
client = SunoClient("your-api-key")
 
# ボーカル入り楽曲
result = client.generate_song(
    prompt="明るいポップソング、夏の海辺をテーマに",
    lyrics="""
[Verse 1]
波の音が聞こえてくる
青い空の下で
風が優しく吹いている
夏が始まる
 
[Chorus]
走り出そう、海へ
輝く太陽の下で
今日は最高の一日
忘れられない夏
""",
    style="J-Pop, upbeat, summer vibes",
    title="夏の海辺",
)

2.4 プロンプトエンジニアリング

# 音楽生成プロンプトの構成要素
 
music_prompt_template = {
    "ジャンル": "electronic, jazz, classical, rock, J-Pop, lo-fi, ambient",
    "ムード": "upbeat, melancholic, energetic, relaxing, dramatic, ethereal",
    "テンポ": "slow (60-80 BPM), medium (90-120 BPM), fast (130-160 BPM)",
    "楽器": "piano, guitar, synth, strings, drums, bass, brass, flute",
    "構成": "intro, verse, chorus, bridge, outro, build-up, drop",
    "品質修飾": "professional, studio quality, high fidelity, warm tone",
    "参考": "in the style of ..., reminiscent of ..., inspired by ...",
}
 
# 効果的なプロンプト例
effective_prompts = [
    # ジャンル + ムード + 楽器 + テンポ + 品質
    "Lo-fi hip hop beat with jazzy piano chords, mellow saxophone, "
    "vinyl crackle, and soft drum loops. Relaxing study music at 85 BPM. "
    "Warm and nostalgic tone.",
 
    # シーン描写型
    "Soundtrack for walking through a neon-lit Tokyo street at night. "
    "Synthwave with Japanese city pop influences. Electric guitar, "
    "retro synthesizers, and a groovy bassline. 110 BPM.",
 
    # 感情表現型
    "A bittersweet farewell song. Gentle acoustic guitar fingerpicking "
    "with a delicate female vocal melody. Gradually building with soft "
    "strings joining midway. Emotional and cinematic.",
]

2.5 プロンプト最適化の体系的手法

class MusicPromptOptimizer:
    """音楽生成プロンプトの体系的最適化"""
 
    # プロンプト構成要素のテンプレート
    PROMPT_COMPONENTS = {
        "genre": {
            "weight": "高",
            "examples": [
                "electronic", "jazz", "classical", "rock", "hip hop",
                "ambient", "folk", "R&B", "metal", "country",
                "J-Pop", "K-Pop", "bossa nova", "reggae", "funk",
            ],
            "tip": "複数ジャンルの組み合わせで独自性を出す",
        },
        "mood": {
            "weight": "高",
            "examples": [
                "upbeat", "melancholic", "energetic", "peaceful",
                "dark", "ethereal", "nostalgic", "triumphant",
                "mysterious", "playful", "tense", "dreamy",
            ],
            "tip": "感情の変化(例: 'starting calm, building to triumphant')が効果的",
        },
        "instruments": {
            "weight": "中",
            "examples": [
                "acoustic guitar", "electric piano", "synthesizer pads",
                "orchestral strings", "808 bass", "jazz drums",
                "flute", "saxophone", "choir", "harp",
            ],
            "tip": "具体的な楽器名を指定するほど精度が上がる",
        },
        "tempo": {
            "weight": "中",
            "examples": [
                "slow (60-80 BPM)", "moderate (90-110 BPM)",
                "upbeat (120-140 BPM)", "fast (150-170 BPM)",
            ],
            "tip": "BPM数値を明示すると安定する",
        },
        "production": {
            "weight": "低",
            "examples": [
                "studio quality", "lo-fi aesthetic", "vinyl warmth",
                "crisp modern production", "raw and organic",
                "heavily reverbed", "dry and intimate",
            ],
            "tip": "プロダクションスタイルで雰囲気を制御",
        },
    }
 
    def build_prompt(
        self,
        genre: str,
        mood: str,
        instruments: list = None,
        tempo: str = None,
        production: str = None,
        scene: str = None,
        negative: str = None,
    ) -> str:
        """構造化されたプロンプトを構築"""
        parts = [genre]
 
        if mood:
            parts.append(f"{mood} atmosphere")
        if instruments:
            parts.append(f"featuring {', '.join(instruments)}")
        if tempo:
            parts.append(f"at {tempo}")
        if production:
            parts.append(f"with {production} production")
        if scene:
            parts.append(f"evoking {scene}")
 
        prompt = ", ".join(parts) + "."
 
        if negative:
            prompt += f" Avoid: {negative}."
 
        return prompt
 
    def generate_variations(self, base_prompt: str, n: int = 5) -> list:
        """プロンプトのバリエーションを生成"""
        variations = []
        modifiers = [
            "with more emphasis on rhythm",
            "with a darker, moodier tone",
            "with brighter, more uplifting energy",
            "with minimal arrangement",
            "with rich, layered arrangement",
            "with retro vintage feel",
            "with modern futuristic production",
        ]
        import random
        for _ in range(n):
            modifier = random.choice(modifiers)
            variations.append(f"{base_prompt} {modifier}")
        return variations
 
    def evaluate_prompt_quality(self, prompt: str) -> dict:
        """プロンプトの品質を評価"""
        issues = []
        suggestions = []
 
        # 長さチェック
        word_count = len(prompt.split())
        if word_count < 5:
            issues.append("プロンプトが短すぎる(5語未満)")
            suggestions.append("ジャンル、ムード、楽器、テンポを追加")
        elif word_count > 50:
            issues.append("プロンプトが長すぎる可能性(50語超)")
            suggestions.append("最も重要な要素に絞る")
 
        # 必須要素チェック
        has_genre = any(g in prompt.lower() for g in ["rock", "jazz", "pop",
                        "electronic", "classical", "ambient", "hip hop"])
        has_mood = any(m in prompt.lower() for m in ["upbeat", "calm", "dark",
                       "energetic", "relaxing", "dramatic"])
        has_instrument = any(i in prompt.lower() for i in ["guitar", "piano",
                            "drums", "synth", "bass", "strings"])
 
        if not has_genre:
            suggestions.append("ジャンルを明示的に追加")
        if not has_mood:
            suggestions.append("ムード/雰囲気を追加")
        if not has_instrument:
            suggestions.append("主要楽器を指定")
 
        score = 10
        score -= len(issues) * 2
        score -= (3 - sum([has_genre, has_mood, has_instrument])) * 1.5
 
        return {
            "score": max(0, min(10, score)),
            "issues": issues,
            "suggestions": suggestions,
            "has_genre": has_genre,
            "has_mood": has_mood,
            "has_instrument": has_instrument,
            "word_count": word_count,
        }
 
# 使用例
optimizer = MusicPromptOptimizer()
 
prompt = optimizer.build_prompt(
    genre="Lo-fi hip hop",
    mood="nostalgic and cozy",
    instruments=["jazzy piano", "vinyl crackle", "soft drums"],
    tempo="85 BPM",
    production="warm lo-fi",
    scene="studying in a rainy cafe",
)
print(prompt)
# "Lo-fi hip hop, nostalgic and cozy atmosphere, featuring jazzy piano,
#  vinyl crackle, soft drums, at 85 BPM, with warm lo-fi production,
#  evoking studying in a rainy cafe."
 
quality = optimizer.evaluate_prompt_quality(prompt)
print(f"品質スコア: {quality['score']}/10")

2.6 歌詞構造のガイドライン

# Suno / Udio 向け歌詞フォーマットガイド
 
class LyricsFormatter:
    """AI音楽生成向け歌詞フォーマッタ"""
 
    # 歌詞構造タグ
    STRUCTURE_TAGS = {
        "[Intro]": "楽器のみのイントロ",
        "[Verse]": "Aメロ(物語の展開部分)",
        "[Verse 1]": "Aメロ1番",
        "[Verse 2]": "Aメロ2番",
        "[Pre-Chorus]": "Bメロ(サビへの助走)",
        "[Chorus]": "サビ(最も印象的な部分)",
        "[Bridge]": "ブリッジ(転調・展開部分)",
        "[Outro]": "アウトロ",
        "[Instrumental]": "楽器のみの間奏",
        "[Hook]": "フック(キャッチーな繰り返し)",
        "[Breakdown]": "ブレイクダウン(音を落とす部分)",
        "[Drop]": "ドロップ(EDMの盛り上がり)",
        "[Rap]": "ラップパート",
        "[Spoken]": "語り/台詞パート",
    }
 
    @staticmethod
    def create_song_structure(style: str = "pop") -> str:
        """ジャンル別の推奨構造テンプレート"""
        structures = {
            "pop": "[Intro]\n[Verse 1]\n[Pre-Chorus]\n[Chorus]\n"
                   "[Verse 2]\n[Pre-Chorus]\n[Chorus]\n[Bridge]\n[Chorus]\n[Outro]",
            "rock": "[Intro]\n[Verse 1]\n[Chorus]\n[Verse 2]\n"
                    "[Chorus]\n[Instrumental]\n[Chorus]\n[Outro]",
            "edm": "[Intro]\n[Breakdown]\n[Drop]\n[Breakdown]\n"
                   "[Drop]\n[Outro]",
            "ballad": "[Intro]\n[Verse 1]\n[Verse 2]\n[Chorus]\n"
                      "[Verse 3]\n[Chorus]\n[Bridge]\n[Chorus]\n[Outro]",
            "rap": "[Intro]\n[Verse 1]\n[Hook]\n[Verse 2]\n"
                   "[Hook]\n[Bridge]\n[Verse 3]\n[Hook]\n[Outro]",
        }
        return structures.get(style, structures["pop"])
 
    @staticmethod
    def format_lyrics(raw_lyrics: str, style: str = "pop") -> str:
        """生テキストの歌詞を構造化フォーマットに変換"""
        lines = [l.strip() for l in raw_lyrics.strip().split("\n") if l.strip()]
 
        if len(lines) < 4:
            return f"[Verse]\n{raw_lyrics}"
 
        formatted = []
        chunk_size = 4  # 4行ずつをセクションに
        sections = ["Verse 1", "Chorus", "Verse 2", "Chorus", "Bridge", "Chorus"]
 
        for i, section in enumerate(sections):
            start = i * chunk_size
            end = start + chunk_size
            if start >= len(lines):
                break
            section_lines = lines[start:end]
            formatted.append(f"[{section}]")
            formatted.extend(section_lines)
            formatted.append("")
 
        return "\n".join(formatted)
 
# 使用例
formatter = LyricsFormatter()
structure = formatter.create_song_structure("pop")
print(structure)

3. 音楽生成ワークフロー

3.1 プロダクション向けワークフロー

AI音楽制作ワークフロー
==================================================

Phase 1: 構想・プロンプト設計
    │
    ├── ジャンル、ムード、テンポの決定
    ├── リファレンス楽曲の選定
    └── プロンプト作成(複数バリエーション)
    │
    ▼
Phase 2: 生成・選定
    │
    ├── 複数バリエーションを生成(5-10候補)
    ├── ベスト候補の選定
    └── 必要に応じてプロンプト調整・再生成
    │
    ▼
Phase 3: 後処理・編集
    │
    ├── ステム分離(ボーカル/伴奏)
    ├── EQ・コンプレッション調整
    ├── 不要部分のカット・構成変更
    └── マスタリング
    │
    ▼
Phase 4: 統合・仕上げ
    │
    ├── 他の音源との統合
    ├── 最終ミックス
    └── 各フォーマットでのエクスポート
==================================================

3.2 自動化されたワークフロー実装

import os
from pathlib import Path
from dataclasses import dataclass
from typing import Optional
 
@dataclass
class MusicGenerationConfig:
    """音楽生成の設定"""
    prompt: str
    duration: float = 30.0
    n_candidates: int = 5
    temperature: float = 1.0
    cfg_coef: float = 3.0
    output_dir: str = "./output"
    target_lufs: float = -14.0
 
class AutoMusicPipeline:
    """自動音楽制作パイプライン"""
 
    def __init__(self, model_name: str = "facebook/musicgen-large"):
        from audiocraft.models import MusicGen
        self.model = MusicGen.get_pretrained(model_name)
 
    def run(self, config: MusicGenerationConfig) -> dict:
        """完全自動パイプラインの実行"""
        output_dir = Path(config.output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)
 
        # Phase 1: 複数候補を生成
        candidates = self._generate_candidates(config)
 
        # Phase 2: 品質スコアリング
        scored = self._score_candidates(candidates, config.prompt)
 
        # Phase 3: ベスト候補の後処理
        best = scored[0]
        processed = self._post_process(best["audio"], config)
 
        # Phase 4: エクスポート
        output_path = output_dir / "final_output.wav"
        self._export(processed, output_path)
 
        return {
            "output_path": str(output_path),
            "n_candidates": len(candidates),
            "best_score": best["score"],
            "all_scores": [s["score"] for s in scored],
        }
 
    def _generate_candidates(self, config):
        """複数候補の生成"""
        self.model.set_generation_params(
            duration=config.duration,
            temperature=config.temperature,
            cfg_coef=config.cfg_coef,
        )
 
        candidates = []
        for i in range(config.n_candidates):
            wav = self.model.generate([config.prompt])
            candidates.append(wav[0])
 
        return candidates
 
    def _score_candidates(self, candidates, prompt):
        """品質スコアリング"""
        import numpy as np
 
        scored = []
        for i, audio in enumerate(candidates):
            # 簡易スコアリング: RMS、ダイナミックレンジ、静寂比率
            samples = audio.cpu().numpy().flatten()
            rms = np.sqrt(np.mean(samples ** 2))
            dynamic_range = np.max(np.abs(samples)) / (rms + 1e-10)
            silence_ratio = np.mean(np.abs(samples) < 0.01)
 
            score = (
                0.4 * min(rms * 10, 1.0) +          # 適度な音量
                0.3 * min(dynamic_range / 10, 1.0) +  # ダイナミックレンジ
                0.3 * (1.0 - silence_ratio)           # 無音が少ない
            )
 
            scored.append({"audio": audio, "score": score, "index": i})
 
        scored.sort(key=lambda x: x["score"], reverse=True)
        return scored
 
    def _post_process(self, audio, config):
        """後処理(正規化、EQ等)"""
        import numpy as np
 
        samples = audio.cpu().numpy()
 
        # ピーク正規化
        peak = np.max(np.abs(samples))
        if peak > 0:
            samples = samples * (0.95 / peak)
 
        # DCオフセット除去
        samples = samples - np.mean(samples)
 
        return samples
 
    def _export(self, audio, output_path):
        """ファイル出力"""
        import soundfile as sf
        import numpy as np
 
        if audio.ndim > 1:
            audio = audio.squeeze()
        sf.write(str(output_path), audio, 32000)
 
# 使用例
pipeline = AutoMusicPipeline()
result = pipeline.run(MusicGenerationConfig(
    prompt="Cinematic orchestral music with emotional strings, "
           "building from soft piano to full orchestra, 90 BPM",
    duration=30.0,
    n_candidates=5,
))
print(f"出力: {result['output_path']}")
print(f"ベストスコア: {result['best_score']:.3f}")

4. ユースケース別実装

4.1 動画BGM自動生成

class VideoBackgroundMusicGenerator:
    """動画コンテンツ向けBGM自動生成"""
 
    # シーンタイプ別のプロンプトテンプレート
    SCENE_PROMPTS = {
        "intro": "corporate intro music, professional, confident, "
                 "modern electronic with clean synths, 110 BPM, 10 seconds",
        "presentation": "soft background music for presentation, "
                        "minimal piano and ambient pads, non-intrusive, "
                        "professional corporate, 90 BPM",
        "action": "high energy action music, driving drums, "
                  "electric guitar riffs, intense and exciting, 140 BPM",
        "emotional": "emotional cinematic music, gentle piano with "
                     "warm strings, touching and heartfelt, 70 BPM",
        "celebration": "upbeat celebration music, happy and bright, "
                       "acoustic guitar with claps and tambourine, 120 BPM",
        "tutorial": "light and friendly tutorial background music, "
                    "ukulele with soft percussion, positive and engaging, "
                    "100 BPM",
        "outro": "gentle outro music, fading out, calm and conclusive, "
                 "soft piano with ambient textures, 80 BPM",
    }
 
    def generate_for_scenes(self, scenes: list) -> dict:
        """シーンリストに基づいてBGMを一括生成"""
        results = {}
        for scene in scenes:
            prompt = self.SCENE_PROMPTS.get(
                scene["type"],
                self.SCENE_PROMPTS["presentation"]
            )
            # duration指定を追加
            if "duration" in scene:
                prompt += f", {scene['duration']} seconds"
 
            results[scene["name"]] = {
                "prompt": prompt,
                "type": scene["type"],
            }
        return results
 
    def generate_loopable(self, prompt: str, duration: float = 30.0) -> str:
        """ループ可能なBGMを生成"""
        loop_prompt = prompt + ". Seamless loop, consistent energy level."
        # 実際にはここでモデルを呼び出す
        return loop_prompt
 
# 使用例
bgm_gen = VideoBackgroundMusicGenerator()
scenes = [
    {"name": "intro", "type": "intro", "duration": 10},
    {"name": "main_content", "type": "tutorial", "duration": 120},
    {"name": "ending", "type": "outro", "duration": 15},
]
bgm_plan = bgm_gen.generate_for_scenes(scenes)

4.2 ゲーム音楽の動的生成

class GameMusicEngine:
    """ゲーム向け動的音楽生成エンジン"""
 
    # ゲーム状態に対応する音楽パラメータ
    GAME_STATES = {
        "exploration": {
            "prompt": "peaceful exploration music, fantasy RPG, "
                      "gentle flute and harp, ambient nature sounds",
            "energy": 0.3,
            "tempo": "70 BPM",
        },
        "combat": {
            "prompt": "intense battle music, epic orchestral, "
                      "pounding drums, aggressive brass, urgent strings",
            "energy": 0.9,
            "tempo": "150 BPM",
        },
        "boss_fight": {
            "prompt": "epic boss battle music, heavy metal meets orchestra, "
                      "choir chanting, double bass drums, distorted guitars",
            "energy": 1.0,
            "tempo": "170 BPM",
        },
        "town": {
            "prompt": "medieval town music, cheerful and bustling, "
                      "acoustic guitar, fiddle, accordion, tavern atmosphere",
            "energy": 0.5,
            "tempo": "110 BPM",
        },
        "dungeon": {
            "prompt": "dark dungeon ambient music, eerie and mysterious, "
                      "deep drones, distant echoes, subtle percussion",
            "energy": 0.4,
            "tempo": "60 BPM",
        },
        "victory": {
            "prompt": "triumphant victory fanfare, heroic brass, "
                      "celebratory orchestral, rising melody",
            "energy": 0.8,
            "tempo": "120 BPM",
        },
    }
 
    def get_music_for_state(self, game_state: str,
                             intensity: float = 1.0) -> dict:
        """ゲーム状態に応じた音楽パラメータを取得"""
        state_config = self.GAME_STATES.get(
            game_state, self.GAME_STATES["exploration"]
        )
 
        # 強度に応じてプロンプトを調整
        prompt = state_config["prompt"]
        if intensity > 0.7:
            prompt += " More intense and dramatic."
        elif intensity < 0.3:
            prompt += " More subdued and calm."
 
        return {
            "prompt": prompt,
            "energy": state_config["energy"] * intensity,
            "tempo": state_config["tempo"],
        }
 
    def create_transition(self, from_state: str, to_state: str,
                           duration_seconds: float = 5.0) -> str:
        """状態遷移時のトランジション音楽プロンプト"""
        from_config = self.GAME_STATES.get(from_state, {})
        to_config = self.GAME_STATES.get(to_state, {})
 
        return (
            f"Musical transition from {from_config.get('prompt', '')} "
            f"to {to_config.get('prompt', '')}, "
            f"smooth crossfade, {duration_seconds} seconds"
        )

5. 比較表

5.1 主要音楽生成サービス比較

項目 Suno Udio MusicGen Stable Audio
種別 SaaS SaaS OSS SaaS/OSS
ボーカル生成 対応 対応 非対応 非対応
歌詞入力 対応 対応 非対応 非対応
最大長 ~4分 ~2分 30秒(標準) 190秒
品質 高い 高い 中〜高 中〜高
カスタマイズ プロンプト プロンプト コード制御 プロンプト
商用利用 有料プラン 有料プラン MIT License 条件付き
ローカル実行 不可 不可 可能 Open版可能
GPU要件 不要 不要 16GB+ VRAM 8GB+ VRAM
API あり あり Python あり

5.2 用途別推奨サービス

ユースケース 推奨 理由
ボーカル入り楽曲 Suno / Udio 歌声生成に対応
BGM/インスト MusicGen / Stable Audio 楽器音の品質が高い
プロトタイプ Suno 簡単操作、高品質
研究開発 MusicGen OSS、カスタマイズ可能
ゲーム音楽 Stable Audio ループ音楽に対応
動画BGM Suno / Stable Audio 長さ・ムード制御が容易
商用利用(低コスト) MusicGen MIT License、無料
ブランドサウンド MusicGen + ファインチューニング 独自スタイルの学習

5.3 モデルサイズ別性能比較(MusicGen)

モデル パラメータ VRAM 生成速度(30秒) 品質 推奨用途
small 300M ~4GB ~5秒 プロトタイプ、テスト
medium 1.5B ~8GB ~15秒 中〜高 バランス重視
large 3.3B ~16GB ~30秒 本番品質
melody 1.5B ~8GB ~15秒 高(メロディ) メロディ条件付き
stereo-small 300M ~4GB ~8秒 ステレオ出力
stereo-large 3.3B ~16GB ~40秒 高品質ステレオ

6. トラブルシューティング

6.1 よくある問題と解決策

問題: 生成された音楽にノイズやアーティファクトが含まれる
==================================================
原因:
- 温度パラメータが高すぎる
- CFG係数が不適切
- モデルサイズが小さい

解決策:
1. temperature を 0.8-1.0 の範囲に設定
2. cfg_coef を 3.0-5.0 の範囲で調整
3. large モデルを使用
4. MultiBandDiffusion (MBD) デコーダで品質向上:
   mbd = MultiBandDiffusion.get_mbd_musicgen()
   wav = mbd.tokens_to_wav(tokens)
==================================================

問題: プロンプトに一致しない音楽が生成される
==================================================
原因:
- プロンプトが曖昧
- CFG係数が低すぎる
- モデルの理解範囲外の指示

解決策:
1. プロンプトを具体化(ジャンル+楽器+テンポ+ムード)
2. cfg_coef を 4.0-6.0 に上げる
3. 英語プロンプトを使用(学習データの大部分が英語)
4. 段階的に条件を追加して生成結果を確認
==================================================

問題: MusicGenでGPUメモリ不足 (OOM) が発生する
==================================================
原因:
- モデルサイズが大きい
- 生成時間が長い
- バッチサイズが大きい

解決策:
1. 小さいモデルに切り替え: musicgen-small (300M)
2. duration を短く(15秒以下)
3. バッチサイズを1に
4. float16で実行: model.to(torch.float16)
5. CPU処理(遅いが安定): model.to("cpu")
==================================================

7. アンチパターン

7.1 アンチパターン: 曖昧なプロンプト

# BAD: 曖昧すぎるプロンプト
bad_prompts = [
    "いい感じの曲を作って",          # 何が「いい感じ」か不明
    "音楽",                         # 情報量ゼロ
    "かっこいいロック",              # 具体性不足
]
 
# GOOD: 具体的で構造化されたプロンプト
good_prompts = [
    # ジャンル + テンポ + 楽器 + ムード + 品質
    "Energetic J-Rock with distorted electric guitars, driving drums at 150 BPM, "
    "powerful bass riffs, and anthemic chorus melodies. "
    "Stadium rock energy with modern production quality.",
 
    # シーン + 詳細 + 技術指定
    "Ambient electronic music for a sci-fi movie scene. "
    "Deep sub-bass drones, ethereal pad synths, glitchy percussion elements, "
    "and distant vocal textures. Dark and mysterious atmosphere. "
    "Slow tempo around 70 BPM with evolving textures.",
]

7.2 アンチパターン: 生成物の無加工使用

# BAD: AI生成音楽をそのまま使用
def bad_workflow(prompt):
    audio = music_gen.generate(prompt)
    publish(audio)  # 品質のバラつき、著作権リスク
 
# GOOD: 後処理パイプラインを通す
def good_workflow(prompt, n_candidates=5):
    # 1. 複数候補生成
    candidates = [music_gen.generate(prompt) for _ in range(n_candidates)]
 
    # 2. 品質スコアリング(自動 + 手動)
    scored = []
    for i, audio in enumerate(candidates):
        score = auto_quality_score(audio)  # CLAP Score等
        scored.append((score, i, audio))
    scored.sort(reverse=True)
 
    # 3. ベスト候補を選択
    best_audio = scored[0][2]
 
    # 4. 後処理
    processed = apply_effects(best_audio, [
        ("normalize", {"target_db": -14}),
        ("eq", {"low_cut": 30, "high_cut": 18000}),
        ("compress", {"threshold": -20, "ratio": 3}),
    ])
 
    # 5. 最終確認
    return processed

7.3 アンチパターン: 著作権の確認を怠る

# BAD: 著作権状況を確認せずに商用利用
def bad_commercial_use(service, prompt):
    audio = service.generate(prompt)
    sell(audio)  # ライセンス確認なし
 
# GOOD: サービスごとのライセンスを確認
def good_commercial_use(service, prompt):
    # サービス別ライセンスチェック
    license_check = {
        "suno_free": {"commercial": False, "credit_required": True},
        "suno_pro": {"commercial": True, "credit_required": False},
        "musicgen": {"commercial": True, "license": "MIT"},
        "stable_audio_free": {"commercial": False},
        "stable_audio_pro": {"commercial": True, "terms": "check website"},
    }
 
    plan = license_check.get(service.plan)
    if not plan or not plan.get("commercial"):
        raise ValueError(
            f"商用利用不可: {service.plan}。有料プランにアップグレードしてください。"
        )
 
    audio = service.generate(prompt)
 
    # メタデータにAI生成であることを記録
    metadata = {
        "generator": service.name,
        "prompt": prompt,
        "license": plan.get("license", "proprietary"),
        "ai_generated": True,
        "generation_date": datetime.now().isoformat(),
    }
 
    return audio, metadata

8. ベストプラクティス

8.1 音楽生成のベストプラクティス

プロンプト設計:
==================================================
1. 英語で記述する(学習データの大部分が英語)
2. ジャンル→ムード→楽器→テンポの順で記述
3. 具体的なBPM値を指定する
4. ネガティブプロンプト(避けたい要素)も活用
5. 参照楽曲のスタイルを具体的に描写

品質管理:
==================================================
1. 必ず複数候補(5-10)を生成して選定
2. 自動品質スコアリング + 人間の聴感評価を組み合わせる
3. CLAP Scoreでプロンプト一致度を定量評価
4. ラウドネス正規化(-14 LUFS)を最終出力に適用
5. 最低限のEQ処理(ローカット80Hz、ハイカット18kHz)

ワークフロー:
==================================================
1. プロンプト反復(粗い指定→微調整→最終版)
2. 生成→ステム分離→個別編集→再合成のフロー
3. AIのガイド提案結果を人間が最終判断
4. 生成ログ(プロンプト、パラメータ、スコア)を記録
5. 成功パターンのプロンプトをテンプレート化

実践演習

演習1: 基本的な実装

以下の要件を満たすコードを実装してください。

要件:

  • 入力データの検証を行うこと
  • エラーハンドリングを適切に実装すること
  • テストコードも作成すること
# 演習1: 基本実装のテンプレート
class Exercise1:
    """基本的な実装パターンの演習"""
 
    def __init__(self):
        self.data = []
 
    def validate_input(self, value):
        """入力値の検証"""
        if value is None:
            raise ValueError("入力値がNoneです")
        return True
 
    def process(self, value):
        """データ処理のメインロジック"""
        self.validate_input(value)
        self.data.append(value)
        return self.data
 
    def get_results(self):
        """処理結果の取得"""
        return {
            'count': len(self.data),
            'data': self.data
        }
 
# テスト
def test_exercise1():
    ex = Exercise1()
    assert ex.process(1) == [1]
    assert ex.process(2) == [1, 2]
    assert ex.get_results()['count'] == 2
 
    try:
        ex.process(None)
        assert False, "例外が発生するべき"
    except ValueError:
        pass
 
    print("全テスト合格!")
 
test_exercise1()

演習2: 応用パターン

基本実装を拡張して、以下の機能を追加してください。

# 演習2: 応用パターン
from typing import List, Dict, Optional
from datetime import datetime
 
class AdvancedExercise:
    """応用パターンの演習"""
 
    def __init__(self, max_size: int = 100):
        self._items: List[Dict] = []
        self._max_size = max_size
        self._created_at = datetime.now()
 
    def add(self, key: str, value: any) -> bool:
        """アイテムの追加(サイズ制限付き)"""
        if len(self._items) >= self._max_size:
            return False
        self._items.append({
            'key': key,
            'value': value,
            'timestamp': datetime.now().isoformat()
        })
        return True
 
    def find(self, key: str) -> Optional[Dict]:
        """キーによる検索"""
        for item in reversed(self._items):
            if item['key'] == key:
                return item
        return None
 
    def remove(self, key: str) -> bool:
        """キーによる削除"""
        for i, item in enumerate(self._items):
            if item['key'] == key:
                self._items.pop(i)
                return True
        return False
 
    def stats(self) -> Dict:
        """統計情報"""
        return {
            'total_items': len(self._items),
            'max_size': self._max_size,
            'usage_percent': len(self._items) / self._max_size * 100,
            'uptime': str(datetime.now() - self._created_at)
        }
 
# テスト
def test_advanced():
    ex = AdvancedExercise(max_size=3)
    assert ex.add("a", 1) == True
    assert ex.add("b", 2) == True
    assert ex.add("c", 3) == True
    assert ex.add("d", 4) == False  # サイズ制限
    assert ex.find("b")['value'] == 2
    assert ex.remove("b") == True
    assert ex.find("b") is None
    stats = ex.stats()
    assert stats['total_items'] == 2
    print("応用テスト全合格!")
 
test_advanced()

演習3: パフォーマンス最適化

以下のコードのパフォーマンスを改善してください。

# 演習3: パフォーマンス最適化
import time
from functools import lru_cache
 
# 最適化前(O(n^2))
def slow_search(data: list, target: int) -> int:
    """非効率な検索"""
    for i in range(len(data)):
        for j in range(i + 1, len(data)):
            if data[i] + data[j] == target:
                return (i, j)
    return (-1, -1)
 
# 最適化後(O(n))
def fast_search(data: list, target: int) -> tuple:
    """ハッシュマップを使った効率的な検索"""
    seen = {}
    for i, num in enumerate(data):
        complement = target - num
        if complement in seen:
            return (seen[complement], i)
        seen[num] = i
    return (-1, -1)
 
# ベンチマーク
def benchmark():
    import random
    data = list(range(5000))
    random.shuffle(data)
    target = data[100] + data[4000]
 
    start = time.time()
    result1 = slow_search(data, target)
    slow_time = time.time() - start
 
    start = time.time()
    result2 = fast_search(data, target)
    fast_time = time.time() - start
 
    print(f"非効率版: {slow_time:.4f}秒")
    print(f"効率版:   {fast_time:.6f}秒")
    print(f"高速化率: {slow_time/fast_time:.0f}倍")
 
benchmark()

ポイント:

  • アルゴリズムの計算量を意識する
  • 適切なデータ構造を選択する
  • ベンチマークで効果を測定する

9. FAQ

Q1: AI生成音楽の著作権はどうなりますか?

法律はまだ発展途上ですが、2025-2026年時点の一般的な見解は次のとおりです。(1) AI生成物自体の著作権: 多くの法域で、AIが自律的に生成したものには著作権が発生しないとされています。(2) プロンプト作成者の権利: 十分な創作的寄与があれば、一部権利が認められる可能性があります。(3) 各サービスの利用規約: Sunoの有料プランでは商用利用が許可され、生成物の使用権がユーザーに付与されます。MusicGenはMITライセンスですが、学習データに関する議論は継続中です。商用利用時は必ず利用規約を確認してください。

Q2: MusicGenのファインチューニングは可能ですか?

可能です。Meta公式のaudiocraftリポジトリにファインチューニングのためのスクリプトが含まれています。手順は、(1) 学習データの準備(音声ファイル + テキスト説明のペア)、(2) audiocraft_trainer の設定、(3) 既存のチェックポイントからの学習再開。ただし、large モデルのファインチューニングには32GB以上のVRAMが必要です。小規模データでの学習はsmallモデルから始めることを推奨します。

Q3: 生成音楽の品質を自動評価する方法はありますか?

主な評価指標として、(1) FAD(Frechet Audio Distance): 生成音楽と参照音楽の分布間距離、(2) CLAP Score: テキストと音声の意味的一致度、(3) KL Divergence: ジャンル/楽器分類の分布差。ただし、音楽の品質は主観的な要素が大きく、自動指標だけでは不十分です。実用的には、自動スコアでの粗い選別 + 人間による聴感評価の組み合わせが最も効果的です。

Q4: 生成音楽を商用コンテンツ(YouTube動画、広告等)で使用する際の注意点は?

(1) 使用するサービスの商用利用ライセンスを確認する(Suno Proプラン等)。(2) Content IDシステムでの誤検出に備え、生成証明(プロンプト、生成日時、サービス名)を保持する。(3) 他の楽曲と酷似した出力がないか聴感確認する。(4) AI生成であることの開示義務がある場合は遵守する。(5) 定期的にサービスの利用規約の更新を確認する。

Q5: 長い楽曲(3分以上)を生成するにはどうすればよいですか?

MusicGenの標準は30秒ですが、以下の方法で長尺化が可能です。(1) Continuation機能: 生成した音声の末尾数秒を入力として続きを生成する連鎖方式。(2) Suno/Udio: 最大4分の楽曲を一度に生成可能。(3) セクション結合: Intro→Verse→Chorusを個別に生成し、クロスフェードで結合。(4) ループ生成: 30秒のループ素材を生成し、DAWで構成を組み立てる。品質面ではSuno/Udieが最も安定した長尺出力を実現しています。

Q6: AI音楽生成の計算コストを最適化するには?

(1) 小さいモデルから始める: musicgen-smallで十分な品質が得られる場合も多い。(2) バッチ生成: 複数プロンプトを一度に処理してGPU効率を上げる。(3) キャッシュ: 同じプロンプトの結果をキャッシュし、再生成を避ける。(4) INT8量子化: モデルを量子化してVRAM使用量を削減。(5) スポットインスタンス: クラウドGPU(AWS, GCP)のスポットインスタンスでコスト削減。1曲あたりの生成コストは、GPU使用($0.5-2/時)× 生成時間で算出できます。


FAQ

Q1: このトピックを学ぶ上で最も重要なポイントは何ですか?

実践的な経験を積むことが最も重要です。理論だけでなく、実際にコードを書いて動作を確認することで理解が深まります。

Q2: 初心者がよく陥る間違いは何ですか?

基礎を飛ばして応用に進むことです。このガイドで説明している基本概念をしっかり理解してから、次のステップに進むことをお勧めします。

Q3: 実務ではどのように活用されていますか?

このトピックの知識は、日常的な開発業務で頻繁に活用されます。特にコードレビューやアーキテクチャ設計の際に重要になります。


まとめ

項目 要点
技術基盤 コーデック言語モデル型と拡散モデル型の2大アプローチ
Suno/Udio ボーカル+歌詞対応。商用プランあり
MusicGen Meta OSS。コード制御可能、ファインチューニング対応
プロンプト ジャンル+ムード+テンポ+楽器+品質修飾を具体的に
ワークフロー 複数生成→選定→後処理→仕上げの4段階
著作権 サービス利用規約を確認。法整備は進行中
品質管理 CLAP Score + 聴感評価の組み合わせ
長尺化 Continuation機能 or Suno/Udio の直接生成

次に読むべきガイド

参考文献

  1. Copet, J., et al. (2023). "Simple and Controllable Music Generation" — MusicGen論文。テキスト条件付き音楽生成のベースライン
  2. Agostinelli, A., et al. (2023). "MusicLM: Generating Music From Text" — Google MusicLM。テキストからの高品質音楽生成
  3. Evans, Z., et al. (2024). "Stable Audio: Fast Timing-Conditioned Latent Audio Diffusion" — Stable Audio。潜在拡散モデルベースの音声生成
  4. Défossez, A., et al. (2023). "High Fidelity Neural Audio Compression" — Encodec論文。音楽生成の基盤となるニューラル音声コーデック
  5. Wu, Y., et al. (2023). "Large-Scale Contrastive Language-Audio Pretraining with Feature Fusion and Keyword-to-Caption Augmentation" — CLAP論文。テキスト-音声の対照学習