AI 音乐与音频生成完全教程

教程简介

零基础AI音乐与音频生成完全教程,涵盖AI音乐生成技术原理、Suno AI音乐创作、Udio音乐生成、Stable Audio开源方案、MusicGen模型、AI音效生成、语音合成进阶、BGM自动配乐、音频风格迁移、版权与商业化等核心技能,配有AI音乐创作工作流与自动化配乐系统实战项目,适合音乐创作者和AI开发者系统学习。

AI 音乐与音频生成完全教程

零基础掌握 AI 音乐与音频生成的完整指南,从技术原理到实战项目,系统构建你的 AI 音频技能栈。


目录


第一章:AI 音乐生成技术概述

1.1 什么是 AI 音乐生成

AI 音乐生成是指利用人工智能技术,通过算法和模型自动创作音乐的过程。它可以生成旋律、和声、节奏、编曲,甚至完整的歌曲。AI 音乐生成的核心在于让机器理解音乐的结构、情感和风格,从而创造出具有艺术价值的作品。

1.2 核心技术架构

现代 AI 音乐生成主要依赖以下技术:

1. Transformer 架构

Transformer 是当前 AI 音乐生成的核心架构。它通过自注意力机制捕捉音乐序列中的长距离依赖关系。在音乐领域,输入序列可以是 MIDI 音符、音频 token 或频谱特征。

2. 扩散模型(Diffusion Models)

Stable Audio 等模型使用扩散模型生成音频波形。扩散模型通过逐步去噪的过程,从随机噪声生成高质量的音频信号。

3. VAE(变分自编码器)

VAE 用于学习音乐的潜在表示,将高维音频数据压缩到低维空间,便于生成和风格迁移。

4. 自回归模型

MusicGen 等模型使用自回归方式逐步生成音频 token,每个 token 的生成都依赖于之前的输出。

1.3 音频表示方式

AI 模型处理音频时,需要将声音转化为数值表示:

原始波形 (Waveform) → 采样率 44.1kHz,每个采样点为浮点数
梅尔频谱 (Mel Spectrogram) → 时频表示,适合视觉模型处理
音频 Token → 离散化表示,适合 Transformer 处理
MIDI → 符号化表示,记录音符、力度、时值

1.4 主流工具与平台对比

平台/工具 类型 特点 适用场景
Suno AI 在线平台 文本生成完整歌曲,含人声 快速创作、Demo 制作
Udio 在线平台 高质量音频,风格多样 专业音乐制作辅助
Stable Audio 开源模型 可本地部署,可控性强 开发者、研究者
MusicGen 开源模型 Meta 出品,轻量高效 音频应用开发
Bark 开源模型 语音合成,多语言 播客、有声读物
AIVA 在线平台 古典音乐专精 影视配乐

第二章:Suno AI 音乐创作实战

2.1 Suno AI 简介

Suno AI 是目前最流行的 AI 音乐生成平台之一,能够根据文本描述生成包含人声的完整歌曲。它支持多种音乐风格、语言和情感表达。

2.2 注册与基础使用

步骤 1:访问 Suno AI

  1. 打开浏览器访问 suno.com
  2. 点击 "Sign Up" 注册账号(支持 Google、Discord、Microsoft 登录)
  3. 免费账户每天有 10 次生成机会

步骤 2:使用 "Custom Mode"

在 Suno 的 Custom Mode 中,你可以精确控制:

  • Song Title:歌曲标题
  • Style of Music:音乐风格描述
  • Lyrics:歌词内容
  • Tags:标签(可选)

2.3 提示词工程(Prompt Engineering)

有效的提示词是生成高质量音乐的关键。

风格描述模板:

[主风格], [子风格], [情感], [节奏], [乐器], [人声类型]

示例:
- Electronic pop, synthwave, upbeat, 128 BPM, synthesizers, female vocal
- Chinese folk, traditional, peaceful, guzheng and erhu, male vocal
- Jazz fusion, complex harmonies, saxophone, piano trio

歌词结构标记:

[Intro]
(纯音乐前奏)

[Verse 1]
在这城市的角落
霓虹灯闪烁着寂寞
每个人都在寻找
属于自己的那首歌

[Chorus]
让音乐带你飞翔
穿过黑夜到天亮
不管世界多疯狂
我们有音乐的力量

[Verse 2]
时间像沙漏流走
回忆却刻在心头
那些年少的梦想
还在心中发着光

[Chorus]
让音乐带你飞翔
穿过黑夜到天亮
不管世界多疯狂
我们有音乐的力量

[Bridge]
当所有声音都安静
只有心跳在回响
这就是生命的旋律
永远不会被遗忘

[Outro]
(渐弱的吉他尾奏)

2.4 高级技巧

技巧 1:控制歌曲结构

使用结构标记可以让 AI 更好地理解歌曲的段落安排:

[Intro] → 前奏
[Verse] → 主歌
[Pre-Chorus] → 预副歌
[Chorus] → 副歌
[Bridge] → 桥段
[Outro] → 尾奏
[Instrumental] → 间奏
[Break] → 断奏

技巧 2:混合语言歌曲

[Verse 1]
走在东京的街头
霓虹映照着我的脸
Tokyo nights, neon lights
每一个瞬间都值得留恋

[Chorus]
我们一起 dance through the night
在星空下自由地飞
不管明天会怎样
此刻就是最美的回忆

技巧 3:指定乐器独奏

[Guitar Solo]
(电吉他独奏,蓝调风格,情感充沛)

[Piano Bridge]
(钢琴间奏,古典风格,柔和渐弱)

2.5 从文本描述生成完整歌曲

以下是一个完整的创作流程示例:

输入描述:

风格:Indie folk, acoustic, warm, 100 BPM
歌词主题:关于一个程序员在深夜写代码时的灵感迸发
语言:中文
情感:温暖、治愈

生成结果评估维度:

  • 旋律是否流畅
  • 歌词与旋律的契合度
  • 人声质量
  • 整体情感表达
  • 编曲丰富度

2.6 Suno API 调用

对于开发者,Suno 提供了 API 接口用于程序化生成:

import requests
import time
import json

class SunoGenerator:
    """Suno AI 音乐生成客户端"""
    
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://api.suno.ai/v1"
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
    
    def generate_song(self, prompt, lyrics=None, style=None, title=None):
        """
        生成歌曲
        
        Args:
            prompt: 音乐描述
            lyrics: 歌词内容(可选)
            style: 风格标签(可选)
            title: 歌曲标题(可选)
        
        Returns:
            dict: 生成任务信息
        """
        payload = {
            "prompt": prompt,
            "make_instrumental": lyrics is None
        }
        
        if lyrics:
            payload["lyrics"] = lyrics
        if style:
            payload["style"] = style
        if title:
            payload["title"] = title
        
        response = requests.post(
            f"{self.base_url}/generate",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()
    
    def check_status(self, task_id):
        """检查生成状态"""
        response = requests.get(
            f"{self.base_url}/status/{task_id}",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()
    
    def wait_for_completion(self, task_id, timeout=300):
        """等待生成完成"""
        start = time.time()
        while time.time() - start < timeout:
            status = self.check_status(task_id)
            if status.get("status") == "completed":
                return status
            elif status.get("status") == "failed":
                raise Exception(f"生成失败: {status.get('error')}")
            time.sleep(5)
        raise TimeoutError("生成超时")


# 使用示例
if __name__ == "__main__":
    generator = SunoGenerator("your-api-key")
    
    result = generator.generate_song(
        prompt="温暖的民谣风格,木吉他伴奏,治愈系",
        lyrics="""[Verse 1]
清晨的阳光透过窗帘
咖啡的香气弥漫房间
打开电脑开始新的一天
代码在指尖跳跃翩翩

[Chorus]
每一行代码都是诗
每一个bug都是谜
解开了就是最美的旋律
这就是程序员的小幸福""",
        style="indie folk, acoustic, warm",
        title="程序员的小幸福"
    )
    
    print(f"任务已提交: {result['task_id']}")
    completed = generator.wait_for_completion(result['task_id'])
    print(f"歌曲已生成: {completed['audio_url']}")

第三章:Udio 音乐生成深度探索

3.1 Udio 平台特点

Udio 是另一个强大的 AI 音乐生成平台,以其高质量的音频输出和丰富的风格支持著称。相比 Suno,Udio 在以下方面表现突出:

  • 音频质量更高:支持最高 44.1kHz 采样率
  • 风格多样性:支持更细分的音乐风格
  • 编曲精细度:乐器分离度更好
  • 时长控制:支持生成更长的连续音乐

3.2 Udio 创作流程

步骤 1:选择创作模式

Udio 提供两种模式:

  • Simple Mode:输入简单描述即可生成
  • Custom Mode:精确控制歌词、风格、结构

步骤 2:编写有效的提示词

Udio 的提示词结构:

[风格] - [情感] - [节奏] - [乐器] - [人声]

示例:
"Epic orchestral soundtrack, dramatic and heroic, 
fast tempo 140 BPM, full orchestra with brass and strings, 
choir vocals, cinematic feel"

步骤 3:后期编辑与扩展

Udio 支持对生成结果进行:

  • Extend:延长歌曲时长
  • Remix:重新混音
  • Variation:生成变体版本
  • Inpaint:替换特定片段

3.3 Udio 与 Suno 对比分析

                Suno AI              Udio
人声质量        ★★★★☆              ★★★★★
编曲质量        ★★★★☆              ★★★★★
生成速度        ★★★★★              ★★★★☆
风格多样性      ★★★★☆              ★★★★★
中文支持        ★★★★★              ★★★★☆
API 支持        ★★★★☆              ★★★☆☆
价格            免费10次/天          免费100次/月

3.4 Udio 提示词最佳实践

音乐风格分层描述法:

def build_udio_prompt(genre, sub_genre, mood, tempo, instruments, vocals):
    """
    构建 Udio 提示词
    
    Args:
        genre: 主风格(如 Electronic, Rock, Jazz)
        sub_genre: 子风格(如 Synthwave, Progressive, Bebop)
        mood: 情感氛围(如 Energetic, Melancholic, Peaceful)
        tempo: 节奏描述(如 Fast 140 BPM, Slow 70 BPM)
        instruments: 主要乐器列表
        vocals: 人声描述
    
    Returns:
        str: 格式化的提示词
    """
    prompt_parts = [
        f"{sub_genre} {genre}",
        mood,
        tempo,
        ", ".join(instruments),
        vocals
    ]
    return ", ".join(prompt_parts)


# 示例用法
prompt = build_udio_prompt(
    genre="Electronic",
    sub_genre="Lo-fi",
    mood="chill and relaxing",
    tempo="slow 85 BPM",
    instruments=["warm synth pads", "vinyl crackle", "soft piano"],
    vocals="no vocals, instrumental"
)
print(prompt)
# 输出: "Lo-fi Electronic, chill and relaxing, slow 85 BPM, 
#        warm synth pads, vinyl crackle, soft piano, no vocals, instrumental"

第四章:Stable Audio 开源方案部署

4.1 Stable Audio 简介

Stable Audio 是由 Stability AI 开源的音频生成模型,基于潜在扩散模型(Latent Diffusion Model)架构。它支持文本到音频的生成,可以本地部署,适合开发者和研究者。

4.2 环境准备

系统要求:

  • Python 3.9+
  • CUDA 11.7+(NVIDIA GPU)
  • 至少 8GB 显存(推荐 16GB+)
  • 16GB+ 系统内存

安装依赖:

# 创建虚拟环境
python -m venv stable-audio-env
source stable-audio-env/bin/activate

# 安装 PyTorch(CUDA 版本)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 安装 Stable Audio 依赖
pip install stable-audio-tools
pip install einops
pip install torchaudio
pip install soundfile
pip install huggingface_hub

4.3 使用 Hugging Face 加载模型

import torch
import torchaudio
from stable_audio_tools import get_pretrained_model
from stable_audio_tools.inference.generation import generate_diffusion_cond

def load_stable_audio_model():
    """加载 Stable Audio 预训练模型"""
    print("正在加载模型...")
    model, model_config = get_pretrained_model("stabilityai/stable-audio-open-1.0")
    sample_rate = model_config["sample_rate"]
    sample_size = model_config["sample_size"]
    
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = model.to(device)
    print(f"模型已加载到 {device},采样率: {sample_rate}Hz")
    
    return model, model_config, sample_rate, sample_size, device


def generate_audio(model, model_config, sample_rate, sample_size, device, 
                   prompt, duration=10.0, cfg_scale=7.0, steps=100):
    """
    生成音频
    
    Args:
        model: Stable Audio 模型
        model_config: 模型配置
        sample_rate: 采样率
        sample_size: 样本大小
        device: 计算设备
        prompt: 文本描述
        duration: 音频时长(秒)
        cfg_scale: 分类器自由引导强度
        steps: 扩散步数
    
    Returns:
        torch.Tensor: 生成的音频张量
    """
    # 设置生成条件
    conditioning = [{
        "prompt": prompt,
        "seconds_start": 0,
        "seconds_total": duration
    }]
    
    # 生成音频
    output = generate_diffusion_cond(
        model,
        steps=steps,
        cfg_scale=cfg_scale,
        conditioning=conditioning,
        sample_size=sample_rate * int(duration),
        sigma_min=0.3,
        sigma_max=500,
        device=device
    )
    
    # 处理输出
    output = output.to(torch.float32)
    if output.dim() == 3:
        output = output[0]
    
    return output


def save_audio(audio_tensor, sample_rate, output_path):
    """保存音频文件"""
    # 归一化到 [-1, 1] 范围
    audio_tensor = audio_tensor / audio_tensor.abs().max()
    torchaudio.save(output_path, audio_tensor.cpu(), sample_rate)
    print(f"音频已保存到: {output_path}")


# 主程序
if __name__ == "__main__":
    # 加载模型
    model, config, sr, size, device = load_stable_audio_model()
    
    # 定义要生成的音频
    prompts = [
        "A gentle piano melody with soft string accompaniment, peaceful and contemplative, classical style",
        "Upbeat electronic dance music with heavy bass drops and synth leads, festival energy 128 BPM",
        "Rain falling on a tin roof with distant thunder, nature sounds, relaxing ambience",
        "Acoustic guitar fingerpicking, folk style, warm and intimate, campfire vibes"
    ]
    
    # 批量生成
    for i, prompt in enumerate(prompts):
        print(f"\n正在生成第 {i+1} 段音频...")
        print(f"描述: {prompt}")
        
        audio = generate_audio(model, config, sr, size, device, prompt, duration=15.0)
        save_audio(audio, sr, f"output_{i+1}.wav")
    
    print("\n所有音频生成完成!")

4.4 高级配置与调优

import torch
import numpy as np
from stable_audio_tools.inference.generation import generate_diffusion_cond

class StableAudioGenerator:
    """Stable Audio 高级生成器"""
    
    def __init__(self, model_id="stabilityai/stable-audio-open-1.0"):
        from stable_audio_tools import get_pretrained_model
        self.model, self.config = get_pretrained_model(model_id)
        self.sample_rate = self.config["sample_rate"]
        self.sample_size = self.config["sample_size"]
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        self.model = self.model.to(self.device)
    
    def generate_with_params(self, prompt, duration=10.0, 
                             steps=100, cfg_scale=7.0,
                             sigma_min=0.3, sigma_max=500,
                             seed=None):
        """
        带详细参数的音频生成
        
        Args:
            prompt: 文本描述
            duration: 时长(秒)
            steps: 扩散步数(越多质量越高,速度越慢)
            cfg_scale: 引导强度(越高越贴合提示词,但可能失真)
            sigma_min: 最小噪声级别
            sigma_max: 最大噪声级别
            seed: 随机种子(用于可复现生成)
        """
        if seed is not None:
            torch.manual_seed(seed)
        
        conditioning = [{
            "prompt": prompt,
            "seconds_start": 0,
            "seconds_total": duration
        }]
        
        output = generate_diffusion_cond(
            self.model,
            steps=steps,
            cfg_scale=cfg_scale,
            conditioning=conditioning,
            sample_size=int(self.sample_rate * duration),
            sigma_min=sigma_min,
            sigma_max=sigma_max,
            device=self.device,
            seed=seed
        )
        
        output = output.to(torch.float32)
        if output.dim() == 3:
            output = output[0]
        
        return output / output.abs().max()
    
    def generate_variations(self, prompt, num_variations=4, duration=10.0):
        """生成多个变体"""
        variations = []
        for i in range(num_variations):
            audio = self.generate_with_params(
                prompt, duration=duration, seed=i * 42
            )
            variations.append(audio)
        return variations
    
    def generate_with_negative_prompt(self, prompt, negative_prompt, 
                                       duration=10.0, steps=100):
        """
        带负面提示词的生成(通过 cfg_scale 控制)
        注意:Stable Audio 原生不支持负面提示词,
        但可以通过提高 cfg_scale 来增强提示词的影响力
        """
        # 在提示词中加入对比描述来间接实现负面提示
        enhanced_prompt = f"{prompt}, avoid: {negative_prompt}"
        
        return self.generate_with_params(
            enhanced_prompt, 
            duration=duration, 
            steps=steps,
            cfg_scale=9.0  # 提高引导强度
        )


# 使用示例
generator = StableAudioGenerator()

# 生成 Lo-fi 背景音乐
lofi_audio = generator.generate_with_params(
    prompt="Lo-fi hip hop beat, chill and relaxing, vinyl crackle, soft drums, jazzy piano chords, study music",
    duration=30.0,
    steps=150,
    cfg_scale=8.0,
    seed=42
)

torchaudio.save("lofi_background.wav", lofi_audio.cpu(), generator.sample_rate)

第五章:MusicGen 模型实战

5.1 MusicGen 简介

MusicGen 是 Meta(Facebook)推出的开源音乐生成模型,基于 Transformer 架构,使用 EnCodec 音频编解码器将音频压缩为离散 token,然后自回归地生成音乐。

5.2 安装与环境配置

# 安装 audiocraft(包含 MusicGen)
pip install audiocraft

# 或者从源码安装
git clone https://github.com/facebookresearch/audiocraft.git
cd audiocraft
pip install -e .

5.3 基础使用

import torchaudio
from audiocraft.models import MusicGen
from audiocraft.data.audio import audio_write

def generate_music_basic(prompt, duration=8.0, output_path="output.wav"):
    """
    基础音乐生成
    
    Args:
        prompt: 文本描述
        duration: 时长(秒),MusicGen 支持最长 30 秒
        output_path: 输出文件路径
    """
    # 加载模型(首次运行会下载模型)
    model = MusicGen.get_pretrained("facebook/musicgen-small")
    model.set_generation_params(duration=duration)
    
    # 生成音乐
    wav = model.generate([prompt])
    
    # 保存音频
    audio_write(
        output_path.replace('.wav', ''),
        wav[0].cpu(),
        model.sample_rate,
        strategy="loudness"
    )
    print(f"音频已保存: {output_path}")

# 使用示例
generate_music_basic(
    prompt="A cheerful acoustic guitar melody with light percussion, folk style",
    duration=10.0,
    output_path="folk_melody.wav"
)

5.4 三种模型规格对比

from audiocraft.models import MusicGen

# 模型规格选择
MODELS = {
    "small": {
        "id": "facebook/musicgen-small",
        "params": "300M",
        "显存需求": "~4GB",
        "质量": "基础",
        "速度": "最快"
    },
    "medium": {
        "id": "facebook/musicgen-medium", 
        "params": "1.5B",
        "显存需求": "~8GB",
        "质量": "良好",
        "速度": "中等"
    },
    "large": {
        "id": "facebook/musicgen-large",
        "params": "3.3B",
        "显存需求": "~16GB",
        "质量": "最佳",
        "速度": "较慢"
    }
}

def load_model(size="medium"):
    """加载指定规格的 MusicGen 模型"""
    model_info = MODELS[size]
    print(f"加载 {size} 模型 ({model_info['params']} 参数)...")
    model = MusicGen.get_pretrained(model_info["id"])
    return model


# 根据显存选择模型
import torch
if torch.cuda.is_available():
    vram_gb = torch.cuda.get_device_properties(0).total_mem / (1024**3)
    if vram_gb >= 16:
        model = load_model("large")
    elif vram_gb >= 8:
        model = load_model("medium")
    else:
        model = load_model("small")
else:
    model = load_model("small")  # CPU 模式用小模型

5.5 高级生成技巧

import torch
import torchaudio
from audiocraft.models import MusicGen
from audiocraft.data.audio import audio_write

class MusicGenProducer:
    """MusicGen 高级音乐制作器"""
    
    def __init__(self, model_size="medium"):
        self.model = MusicGen.get_pretrained(f"facebook/musicgen-{model_size}")
        self.sample_rate = self.model.sample_rate
    
    def generate_with_continuation(self, prompt, total_duration=30.0, 
                                     chunk_duration=10.0):
        """
        通过续写方式生成长音乐
        
        Args:
            prompt: 初始描述
            total_duration: 总时长
            chunk_duration: 每段时长
        """
        all_audio = []
        current_duration = 0
        
        while current_duration < total_duration:
            remaining = min(chunk_duration, total_duration - current_duration)
            self.model.set_generation_params(duration=remaining)
            
            if current_duration == 0:
                # 第一段:根据提示词生成
                wav = self.model.generate([prompt])
            else:
                # 后续段:基于前一段续写
                wav = self.model.generate_continuation(
                    all_audio[-1][:, -self.sample_rate * 5:],  # 使用最后5秒作为上下文
                    self.sample_rate,
                    descriptions=[prompt]
                )
            
            all_audio.append(wav[0].cpu())
            current_duration += remaining
            print(f"已生成: {current_duration:.1f}/{total_duration:.1f} 秒")
        
        # 拼接所有片段
        full_audio = torch.cat(all_audio, dim=-1)
        return full_audio
    
    def generate_with_melody(self, melody_audio_path, description):
        """
        基于旋律条件生成
        
        Args:
            melody_audio_path: 参考旋律音频文件路径
            description: 音乐描述
        """
        # 加载参考旋律
        melody, sr = torchaudio.load(melody_audio_path)
        
        # 重采样到模型要求的采样率
        if sr != self.sample_rate:
            resampler = torchaudio.transforms.Resample(sr, self.sample_rate)
            melody = resampler(melody)
        
        # 确保单声道
        if melody.shape[0] > 1:
            melody = melody.mean(dim=0, keepdim=True)
        
        self.model.set_generation_params(duration=15.0)
        
        # 使用旋律条件生成
        wav = self.model.generate_with_chroma(
            descriptions=[description],
            melody_wavs=[melody],
            melody_sample_rate=self.sample_rate
        )
        
        return wav[0].cpu()
    
    def batch_generate(self, prompts, duration=10.0):
        """批量生成多段音乐"""
        self.model.set_generation_params(duration=duration)
        wavs = self.model.generate(prompts)
        return [wav.cpu() for wav in wavs]
    
    def generate_song_parts(self, song_structure):
        """
        根据歌曲结构生成各部分
        
        Args:
            song_structure: 歌曲结构字典
            示例: {
                "intro": {"prompt": "gentle piano", "duration": 8},
                "verse": {"prompt": "acoustic guitar, soft vocals", "duration": 15},
                "chorus": {"prompt": "full band, energetic", "duration": 12},
                "outro": {"prompt": "fading piano melody", "duration": 8}
            }
        """
        parts = {}
        for part_name, config in song_structure.items():
            print(f"正在生成 {part_name}...")
            self.model.set_generation_params(duration=config["duration"])
            wav = self.model.generate([config["prompt"]])
            parts[part_name] = wav[0].cpu()
        
        return parts
    
    def assemble_song(self, parts, crossfade_duration=1.0):
        """组装歌曲各部分,带交叉淡化"""
        import torch.nn.functional as F
        
        segments = list(parts.values())
        if len(segments) == 1:
            return segments[0]
        
        crossfade_samples = int(crossfade_duration * self.sample_rate)
        result = segments[0]
        
        for i in range(1, len(segments)):
            # 创建交叉淡化曲线
            fade_out = torch.linspace(1, 0, crossfade_samples)
            fade_in = torch.linspace(0, 1, crossfade_samples)
            
            # 应用交叉淡化
            result_end = result[:, -crossfade_samples:] * fade_out
            next_start = segments[i][:, :crossfade_samples] * fade_in
            
            # 拼接
            crossfaded = result_end + next_start
            result = torch.cat([
                result[:, :-crossfade_samples],
                crossfaded,
                segments[i][:, crossfade_samples:]
            ], dim=-1)
        
        return result


# 使用示例
producer = MusicGenProducer("medium")

# 生成一首完整的歌曲结构
song_structure = {
    "intro": {
        "prompt": "Soft piano chords, atmospheric, cinematic intro",
        "duration": 8
    },
    "verse1": {
        "prompt": "Acoustic guitar fingerpicking, gentle melody, folk style, warm",
        "duration": 15
    },
    "chorus": {
        "prompt": "Full band, uplifting, drums and bass, electric guitar, energetic and emotional",
        "duration": 12
    },
    "verse2": {
        "prompt": "Acoustic guitar and light strings, reflective, folk pop",
        "duration": 15
    },
    "outro": {
        "prompt": "Fading acoustic guitar, soft piano, gentle ending, peaceful",
        "duration": 10
    }
}

parts = producer.generate_song_parts(song_structure)
complete_song = producer.assemble_song(parts, crossfade_duration=2.0)

torchaudio.save("complete_song.wav", complete_song, producer.sample_rate)
print("完整歌曲已保存!")

5.6 MusicGen 模型微调

import torch
from audiocraft.models import MusicGen
from audiocraft.data.dataset import MusicDataset
from torch.utils.data import DataLoader

def finetune_musicgen(train_audio_dir, train_descriptions, 
                       output_dir, epochs=10, lr=1e-4):
    """
    微调 MusicGen 模型
    
    Args:
        train_audio_dir: 训练音频文件目录
        train_descriptions: 训练描述列表
        output_dir: 输出目录
        epochs: 训练轮数
        lr: 学习率
    """
    import os
    from pathlib import Path
    
    # 加载预训练模型
    model = MusicGen.get_pretrained("facebook/musicgen-small")
    
    # 准备数据集
    audio_files = list(Path(train_audio_dir).glob("*.wav"))
    assert len(audio_files) == len(train_descriptions), \
        "音频文件数量必须与描述数量匹配"
    
    # 创建数据加载器
    dataset = MusicDataset(audio_files, train_descriptions, 
                           sample_rate=model.sample_rate, duration=10.0)
    dataloader = DataLoader(dataset, batch_size=4, shuffle=True)
    
    # 设置优化器
    optimizer = torch.optim.AdamW(model.model.parameters(), lr=lr)
    
    # 训练循环
    model.model.train()
    for epoch in range(epochs):
        total_loss = 0
        for batch_idx, (audio, descriptions) in enumerate(dataloader):
            optimizer.zero_grad()
            
            # 前向传播
            loss = model.model.compute_loss(audio, descriptions)
            
            # 反向传播
            loss.backward()
            optimizer.step()
            
            total_loss += loss.item()
            
            if batch_idx % 10 == 0:
                print(f"Epoch {epoch+1}, Batch {batch_idx}, Loss: {loss.item():.4f}")
        
        avg_loss = total_loss / len(dataloader)
        print(f"Epoch {epoch+1} 完成, 平均 Loss: {avg_loss:.4f}")
    
    # 保存微调后的模型
    os.makedirs(output_dir, exist_ok=True)
    torch.save(model.model.state_dict(), 
               os.path.join(output_dir, "finetuned_model.pt"))
    print(f"模型已保存到: {output_dir}")


# 数据集类
class MusicDataset(torch.utils.data.Dataset):
    def __init__(self, audio_files, descriptions, sample_rate=32000, duration=10.0):
        self.audio_files = audio_files
        self.descriptions = descriptions
        self.sample_rate = sample_rate
        self.duration = duration
        self.num_samples = int(sample_rate * duration)
    
    def __len__(self):
        return len(self.audio_files)
    
    def __getitem__(self, idx):
        import torchaudio
        
        audio, sr = torchaudio.load(self.audio_files[idx])
        
        # 重采样
        if sr != self.sample_rate:
            audio = torchaudio.transforms.Resample(sr, self.sample_rate)(audio)
        
        # 单声道
        if audio.shape[0] > 1:
            audio = audio.mean(dim=0, keepdim=True)
        
        # 截取或填充到固定长度
        if audio.shape[-1] > self.num_samples:
            audio = audio[:, :self.num_samples]
        else:
            padding = self.num_samples - audio.shape[-1]
            audio = torch.nn.functional.pad(audio, (0, padding))
        
        return audio, self.descriptions[idx]

第六章:AI 音效生成技术

6.1 音效生成概述

AI 音效生成专注于创建短小的环境音、音效和声景,广泛应用于游戏、影视、播客等领域。

6.2 使用 AudioGen 生成环境音

from audiocraft.models import AudioGen
from audiocraft.data.audio import audio_write

def generate_sound_effects():
    """生成各种音效"""
    model = AudioGen.get_pretrained("facebook/audiogen-medium")
    
    sound_effects = {
        "rain": "Heavy rain falling on a window, indoor ambience, calming",
        "city": "Busy city street, car horns, people talking, urban noise",
        "forest": "Forest ambience, birds singing, leaves rustling, peaceful",
        "ocean": "Ocean waves crashing on shore, seagulls, beach ambience",
        "thunder": "Thunderstorm, heavy rain, lightning strikes, dramatic",
        "cafe": "Coffee shop ambience, espresso machine, quiet conversations",
        "fireplace": "Crackling fireplace, warm and cozy, wood burning",
        "keyboard": "Mechanical keyboard typing, rapid clicking, coding session"
    }
    
    for name, description in sound_effects.items():
        print(f"生成音效: {name}")
        model.set_generation_params(duration=10.0)
        wav = model.generate([description])
        audio_write(f"sfx_{name}", wav[0].cpu(), model.sample_rate, 
                    strategy="loudness")
    
    print("所有音效生成完成!")


def generate_game_sounds():
    """生成游戏音效"""
    model = AudioGen.get_pretrained("facebook/audiogen-medium")
    
    game_sounds = {
        "sword_clash": "Metal sword clanging, combat sound effect, sharp and impactful",
        "magic_spell": "Magical spell casting, ethereal shimmer, fantasy sound effect",
        "explosion": "Explosion sound effect, deep bass boom, debris falling",
        "coin_collect": "Coin pickup sound, bright chime, retro game style",
        "door_open": "Heavy wooden door creaking open, medieval castle",
        "footsteps": "Footsteps on gravel, walking pace, outdoor",
        "water_splash": "Water splash, object falling into water, lake"
    }
    
    model.set_generation_params(duration=3.0)  # 音效通常较短
    
    for name, description in game_sounds.items():
        print(f"生成游戏音效: {name}")
        wav = model.generate([description])
        audio_write(f"game_{name}", wav[0].cpu(), model.sample_rate,
                    strategy="loudness")


if __name__ == "__main__":
    generate_sound_effects()
    generate_game_sounds()

6.3 音效后期处理

import torch
import torchaudio

class SoundEffectProcessor:
    """音效后期处理器"""
    
    def __init__(self, sample_rate=16000):
        self.sample_rate = sample_rate
    
    def normalize(self, audio, target_db=-20.0):
        """标准化音量"""
        rms = torch.sqrt(torch.mean(audio ** 2))
        target_rms = 10 ** (target_db / 20)
        return audio * (target_rms / rms)
    
    def fade_in_out(self, audio, fade_in_ms=50, fade_out_ms=100):
        """添加淡入淡出"""
        fade_in_samples = int(self.sample_rate * fade_in_ms / 1000)
        fade_out_samples = int(self.sample_rate * fade_out_ms / 1000)
        
        # 淡入
        fade_in = torch.linspace(0, 1, fade_in_samples)
        audio[:, :fade_in_samples] *= fade_in
        
        # 淡出
        fade_out = torch.linspace(1, 0, fade_out_samples)
        audio[:, -fade_out_samples:] *= fade_out
        
        return audio
    
    def add_reverb(self, audio, room_size=0.5):
        """简单混响效果"""
        delay_samples = int(self.sample_rate * 0.03)  # 30ms 延迟
        decay = 0.3 * room_size
        
        reverb = torch.zeros_like(audio)
        for i in range(5):  # 5 次反射
            delay = delay_samples * (i + 1)
            gain = decay ** (i + 1)
            if delay < audio.shape[-1]:
                reverb[:, delay:] += audio[:, :-delay] * gain
        
        return audio + reverb * 0.3
    
    def pitch_shift(self, audio, semitones):
        """变调处理"""
        factor = 2 ** (semitones / 12)
        effects = [
            ["pitch", str(semitones * 100)],
            ["rate", str(self.sample_rate)]
        ]
        shifted, _ = torchaudio.sox_effects.apply_effects_tensor(
            audio.unsqueeze(0), self.sample_rate, effects
        )
        return shifted.squeeze(0)
    
    def time_stretch(self, audio, rate=1.0):
        """变速处理"""
        effects = [["tempo", str(rate)]]
        stretched, _ = torchaudio.sox_effects.apply_effects_tensor(
            audio.unsqueeze(0), self.sample_rate, effects
        )
        return stretched.squeeze(0)


# 使用示例
processor = SoundEffectProcessor(sample_rate=16000)
audio, sr = torchaudio.load("game_sword_clash.wav")

# 标准化
audio = processor.normalize(audio, target_db=-16.0)

# 添加淡入淡出
audio = processor.fade_in_out(audio, fade_in_ms=10, fade_out_ms=200)

# 添加混响
audio = processor.add_reverb(audio, room_size=0.6)

torchaudio.save("game_sword_clash_processed.wav", audio, sr)

第七章:语音合成进阶应用

7.1 Bark 语音合成

Bark 是 Suno AI 开源的文本到语音模型,支持多语言、情感表达和音乐元素。

from transformers import AutoProcessor, BarkModel
import torch
import scipy

def setup_bark():
    """初始化 Bark 模型"""
    processor = AutoProcessor.from_pretrained("suno/bark-small")
    model = BarkModel.from_pretrained("suno/bark-small")
    
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = model.to(device)
    
    return processor, model, device


def generate_speech(text, output_path="speech.wav", speaker="v2/zh_speaker_6"):
    """
    生成语音
    
    Args:
        text: 输入文本(支持特殊标记)
        output_path: 输出路径
        speaker: 说话人预设
    """
    processor, model, device = setup_bark()
    
    inputs = processor(text, voice_preset=speaker)
    
    with torch.no_grad():
        audio_array = model.generate(**inputs.to(device))
    
    audio_array = audio_array.cpu().numpy().squeeze()
    sample_rate = model.generation_config.sample_rate
    
    scipy.io.wavfile.write(output_path, rate=sample_rate, data=audio_array)
    print(f"语音已保存: {output_path}")


# 支持的特殊标记
# [laughter] - 笑声
# [sighs] - 叹气
# [gasps] - 倒吸气
# [clears throat] - 清嗓子
# ♪ - 音乐/歌唱

def demo_special_tokens():
    """演示特殊标记用法"""
    examples = {
        "笑声": "你好啊![laughter] 这真是太有趣了!",
        "叹气": "[sighs] 今天又是忙碌的一天啊。",
        "歌唱": "♪ 小星星亮晶晶,满天都是小星星 ♪",
        "混合情感": "哇![gasps] 你真的做到了?[laughter] 太棒了!"
    }
    
    for name, text in examples.items():
        print(f"生成: {name}")
        generate_speech(text, f"bark_{name}.wav")


# 多语言语音生成
def multilingual_speech():
    """多语言语音生成示例"""
    texts = {
        "chinese": "人工智能正在改变我们的生活方式。",
        "english": "Artificial intelligence is transforming our world.",
        "japanese": "人工知能は私たちの生活を変えています。",
        "korean": "인공지능이 우리의 삶을 변화시키고 있습니다."
    }
    
    for lang, text in texts.items():
        generate_speech(text, f"speech_{lang}.wav")

7.2 XTTS 高质量语音克隆

from TTS.api import TTS

class VoiceCloner:
    """语音克隆工具"""
    
    def __init__(self):
        # 加载 XTTS 模型
        self.tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2")
    
    def clone_voice(self, text, reference_audio_path, output_path, 
                     language="zh"):
        """
        克隆语音并生成新语音
        
        Args:
            text: 要合成的文本
            reference_audio_path: 参考音频(用于克隆的声音样本)
            output_path: 输出路径
            language: 语言代码
        """
        self.tts.tts_to_file(
            text=text,
            speaker_wav=reference_audio_path,
            language=language,
            file_path=output_path
        )
        print(f"语音已生成: {output_path}")
    
    def batch_generate(self, text_list, reference_audio, output_dir):
        """批量生成语音"""
        import os
        os.makedirs(output_dir, exist_ok=True)
        
        for i, text in enumerate(text_list):
            output_path = os.path.join(output_dir, f"speech_{i:03d}.wav")
            self.clone_voice(text, reference_audio, output_path)
        
        print(f"批量生成完成,共 {len(text_list)} 个文件")


# 使用示例
cloner = VoiceCloner()

# 克隆一个声音并生成新内容
cloner.clone_voice(
    text="欢迎来到人工智能的世界,让我们一起探索未来的无限可能。",
    reference_audio_path="my_voice_sample.wav",
    output_path="cloned_speech.wav",
    language="zh"
)

# 批量生成播客内容
podcast_scripts = [
    "大家好,欢迎收听本期节目。今天我们来聊聊AI音乐的最新进展。",
    "在过去的一年里,AI音乐生成技术取得了惊人的突破。",
    "从Suno到Udio,从MusicGen到Stable Audio,工具越来越丰富。",
    "这些工具让普通人也能轻松创作出专业水准的音乐作品。",
]

cloner.batch_generate(
    podcast_scripts,
    reference_audio="host_voice.wav",
    output_dir="podcast_episodes"
)

7.3 TTS 与情感控制

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

class EmotionalTTS:
    """情感语音合成"""
    
    EMOTION_PROMPTS = {
        "happy": "用开心、愉快的语气说:",
        "sad": "用悲伤、低沉的语气说:",
        "angry": "用愤怒、激动的语气说:",
        "calm": "用平静、温和的语气说:",
        "excited": "用兴奋、热情的语气说:",
        "whisper": "用轻声、耳语的方式说:"
    }
    
    def synthesize_with_emotion(self, text, emotion="calm", 
                                  output_path="emotional_speech.wav"):
        """带情感的语音合成"""
        # 使用 Bark 的情感控制
        from transformers import AutoProcessor, BarkModel
        import scipy
        
        processor = AutoProcessor.from_pretrained("suno/bark-small")
        model = BarkModel.from_pretrained("suno/bark-small")
        
        # 构建情感化的输入
        emotion_text = f"{self.EMOTION_PROMPTS.get(emotion, '')}{text}"
        
        inputs = processor(emotion_text)
        
        with torch.no_grad():
            audio = model.generate(**inputs)
        
        audio = audio.cpu().numpy().squeeze()
        sample_rate = model.generation_config.sample_rate
        scipy.io.wavfile.write(output_path, rate=sample_rate, data=audio)
        
        return output_path

第八章:BGM 自动配乐系统

8.1 配乐系统架构

自动配乐系统的核心是根据内容的情感、节奏和场景自动匹配或生成合适的背景音乐。

输入内容 → 情感分析 → 场景识别 → 音乐参数确定 → 音乐生成 → 音频混合 → 输出

8.2 情感分析模块

from transformers import pipeline

class EmotionAnalyzer:
    """文本情感分析器"""
    
    def __init__(self):
        self.classifier = pipeline(
            "text-classification",
            model="j-hartmann/emotion-english-distilroberta-base",
            top_k=None
        )
    
    def analyze(self, text):
        """
        分析文本情感
        
        Returns:
            dict: 情感分数映射
        """
        results = self.classifier(text)
        return {r['label']: r['score'] for r in results[0]}
    
    def get_music_params(self, text):
        """
        根据情感分析结果推荐音乐参数
        
        Returns:
            dict: 音乐参数
        """
        emotions = self.analyze(text)
        dominant_emotion = max(emotions, key=emotions.get)
        
        # 情感到音乐参数的映射
        emotion_to_music = {
            "joy": {
                "tempo": "upbeat 120-140 BPM",
                "key": "major",
                "instruments": ["piano", "acoustic guitar", "light percussion"],
                "mood": "cheerful and uplifting",
                "energy": "high"
            },
            "sadness": {
                "tempo": "slow 60-80 BPM",
                "key": "minor",
                "instruments": ["piano", "strings", "cello"],
                "mood": "melancholic and reflective",
                "energy": "low"
            },
            "anger": {
                "tempo": "fast 140-160 BPM",
                "key": "minor",
                "instruments": ["electric guitar", "drums", "bass"],
                "mood": "intense and aggressive",
                "energy": "very high"
            },
            "fear": {
                "tempo": "moderate 90-110 BPM",
                "key": "minor",
                "instruments": ["synthesizer", "strings", "low piano"],
                "mood": "tense and suspenseful",
                "energy": "medium"
            },
            "surprise": {
                "tempo": "variable 100-130 BPM",
                "key": "major",
                "instruments": ["orchestra", "bells", "harp"],
                "mood": "wonder and amazement",
                "energy": "medium-high"
            },
            "neutral": {
                "tempo": "moderate 100-110 BPM",
                "key": "major",
                "instruments": ["piano", "guitar", "light strings"],
                "mood": "calm and pleasant",
                "energy": "medium"
            }
        }
        
        music_params = emotion_to_music.get(dominant_emotion, 
                                             emotion_to_music["neutral"])
        music_params["detected_emotion"] = dominant_emotion
        music_params["emotion_scores"] = emotions
        
        return music_params


# 使用示例
analyzer = EmotionAnalyzer()

texts = [
    "今天是我最开心的一天!我收到了梦寐以求的录取通知书!",
    "雨夜独自坐在窗前,回忆着那些再也回不去的时光。",
    "这个项目已经延期三次了!我们必须立刻找到解决方案!",
    "在深山老林中迷路了,四周一片漆黑,手机也没有信号。"
]

for text in texts:
    params = analyzer.get_music_params(text)
    print(f"\n文本: {text[:30]}...")
    print(f"情感: {params['detected_emotion']}")
    print(f"推荐节奏: {params['tempo']}")
    print(f"推荐乐器: {', '.join(params['instruments'])}")
    print(f"氛围: {params['mood']}")

8.3 视频自动配乐

import torch
import torchaudio
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip
from audiocraft.models import MusicGen

class VideoAutoScorer:
    """视频自动配乐系统"""
    
    def __init__(self, model_size="medium"):
        self.music_model = MusicGen.get_pretrained(f"facebook/musicgen-{model_size}")
        self.emotion_analyzer = EmotionAnalyzer() if 'EmotionAnalyzer' in dir() else None
    
    def analyze_video_scenes(self, video_path):
        """
        分析视频场景(基于时长简单分割)
        
        Returns:
            list: 场景列表
        """
        video = VideoFileClip(video_path)
        duration = video.duration
        
        # 简单按时间分割场景
        scene_duration = 15  # 每个场景15秒
        scenes = []
        
        for start in range(0, int(duration), scene_duration):
            end = min(start + scene_duration, duration)
            scenes.append({
                "start": start,
                "end": end,
                "duration": end - start
            })
        
        return scenes
    
    def generate_bgm_for_scene(self, description, duration):
        """为单个场景生成背景音乐"""
        self.music_model.set_generation_params(duration=duration)
        wav = self.music_model.generate([description])
        return wav[0].cpu()
    
    def generate_full_soundtrack(self, video_path, scene_descriptions, 
                                   output_path="soundtrack.wav"):
        """
        为完整视频生成配乐
        
        Args:
            video_path: 视频文件路径
            scene_descriptions: 各场景的音乐描述列表
            output_path: 输出配乐路径
        """
        scenes = self.analyze_video_scenes(video_path)
        
        soundtrack_parts = []
        for i, scene in enumerate(scenes):
            desc = scene_descriptions[i] if i < len(scene_descriptions) else \
                   "calm background music, neutral"
            
            print(f"生成场景 {i+1} 配乐 ({scene['duration']}秒)...")
            bgm = self.generate_bgm_for_scene(desc, scene['duration'])
            soundtrack_parts.append(bgm)
        
        # 拼接所有场景配乐
        full_soundtrack = torch.cat(soundtrack_parts, dim=-1)
        torchaudio.save(output_path, full_soundtrack, 
                        self.music_model.sample_rate)
        
        return output_path
    
    def mix_with_video(self, video_path, bgm_path, 
                        original_volume=1.0, bgm_volume=0.3,
                        output_path="video_with_bgm.mp4"):
        """将配乐与视频混合"""
        video = VideoFileClip(video_path)
        bgm = AudioFileClip(bgm_path)
        
        # 调整背景音乐时长
        if bgm.duration > video.duration:
            bgm = bgm.subclip(0, video.duration)
        else:
            # 循环播放
            loops_needed = int(video.duration / bgm.duration) + 1
            bgm = bgm.loop(n=loops_needed).subclip(0, video.duration)
        
        # 调整音量
        bgm = bgm.volumex(bgm_volume)
        
        # 混合音频
        if video.audio:
            original = video.audio.volumex(original_volume)
            final_audio = CompositeAudioClip([original, bgm])
        else:
            final_audio = bgm
        
        # 输出
        final_video = video.set_audio(final_audio)
        final_video.write_videofile(output_path, codec='libx264', 
                                     audio_codec='aac')
        
        print(f"视频已保存: {output_path}")


# 使用示例
scorer = VideoAutoScorer("medium")

# 场景描述
scene_descriptions = [
    "Gentle piano, sunrise, peaceful morning, ambient, calm",
    "Upbeat electronic, city traffic, busy streets, energetic",
    "Soft acoustic guitar, park scene, birds singing, natural",
    "Dramatic orchestral, building tension, suspenseful",
    "Uplifting pop, celebration, happy ending, triumphant"
]

# 生成配乐
scorer.generate_full_soundtrack(
    "input_video.mp4",
    scene_descriptions,
    "video_soundtrack.wav"
)

# 混合到视频
scorer.mix_with_video(
    "input_video.mp4",
    "video_soundtrack.wav",
    original_volume=0.8,
    bgm_volume=0.25,
    output_path="final_video.mp4"
)

第九章:音频风格迁移

9.1 风格迁移原理

音频风格迁移是将一个音频的风格(如音色、氛围)应用到另一个音频的内容上,同时保留原始音频的旋律和节奏。

9.2 基于神经网络的风格迁移

import torch
import torch.nn as nn
import torchaudio

class AudioStyleTransfer(nn.Module):
    """音频风格迁移网络"""
    
    def __init__(self, n_fft=2048, hop_length=512):
        super().__init__()
        self.n_fft = n_fft
        self.hop_length = hop_length
        
        # 编码器
        self.encoder = nn.Sequential(
            nn.Conv1d(1, 64, kernel_size=15, stride=1, padding=7),
            nn.InstanceNorm1d(64),
            nn.ReLU(),
            nn.Conv1d(64, 128, kernel_size=15, stride=2, padding=7),
            nn.InstanceNorm1d(128),
            nn.ReLU(),
            nn.Conv1d(128, 256, kernel_size=15, stride=2, padding=7),
            nn.InstanceNorm1d(256),
            nn.ReLU()
        )
        
        # 风格转换层
        self.style_transfer = nn.Sequential(
            nn.Conv1d(256, 256, kernel_size=3, padding=1),
            nn.InstanceNorm1d(256),
            nn.ReLU(),
            nn.Conv1d(256, 256, kernel_size=3, padding=1),
            nn.InstanceNorm1d(256),
            nn.ReLU()
        )
        
        # 解码器
        self.decoder = nn.Sequential(
            nn.ConvTranspose1d(256, 128, kernel_size=15, stride=2, 
                               padding=7, output_padding=1),
            nn.InstanceNorm1d(128),
            nn.ReLU(),
            nn.ConvTranspose1d(128, 64, kernel_size=15, stride=2, 
                               padding=7, output_padding=1),
            nn.InstanceNorm1d(64),
            nn.ReLU(),
            nn.Conv1d(64, 1, kernel_size=15, stride=1, padding=7),
            nn.Tanh()
        )
    
    def forward(self, content, style):
        """
        前向传播
        
        Args:
            content: 内容音频 [batch, 1, samples]
            style: 风格音频 [batch, 1, samples]
        """
        # 提取内容特征
        content_features = self.encoder(content)
        
        # 提取风格特征
        style_features = self.encoder(style)
        
        # 自适应实例归一化(AdaIN)风格迁移
        transferred = self.adaptive_instance_norm(content_features, 
                                                   style_features)
        
        # 转换层
        transferred = self.style_transfer(transferred)
        
        # 解码
        output = self.decoder(transferred)
        
        return output
    
    def adaptive_instance_norm(self, content, style):
        """自适应实例归一化"""
        content_mean = content.mean(dim=-1, keepdim=True)
        content_std = content.std(dim=-1, keepdim=True) + 1e-6
        
        style_mean = style.mean(dim=-1, keepdim=True)
        style_std = style.std(dim=-1, keepdim=True) + 1e-6
        
        normalized = (content - content_mean) / content_std
        return normalized * style_std + style_mean


def transfer_style(content_path, style_path, output_path, 
                    model_path=None, duration=10.0):
    """
    执行风格迁移
    
    Args:
        content_path: 内容音频路径
        style_path: 风格音频路径
        output_path: 输出路径
        model_path: 预训练模型路径(可选)
        duration: 处理时长
    """
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = AudioStyleTransfer().to(device)
    
    if model_path:
        model.load_state_dict(torch.load(model_path))
    model.eval()
    
    # 加载音频
    content, sr1 = torchaudio.load(content_path)
    style, sr2 = torchaudio.load(style_path)
    
    # 统一采样率
    target_sr = 22050
    if sr1 != target_sr:
        content = torchaudio.transforms.Resample(sr1, target_sr)(content)
    if sr2 != target_sr:
        style = torchaudio.transforms.Resample(sr2, target_sr)(style)
    
    # 转为单声道
    if content.shape[0] > 1:
        content = content.mean(dim=0, keepdim=True)
    if style.shape[0] > 1:
        style = style.mean(dim=0, keepdim=True)
    
    # 截取指定时长
    num_samples = int(target_sr * duration)
    content = content[:, :num_samples]
    style = style[:, :num_samples]
    
    # 添加 batch 维度并移到设备
    content = content.unsqueeze(0).to(device)
    style = style.unsqueeze(0).to(device)
    
    # 执行迁移
    with torch.no_grad():
        output = model(content, style)
    
    # 保存结果
    output = output.squeeze(0).cpu()
    output = output / output.abs().max()  # 归一化
    torchaudio.save(output_path, output, target_sr)
    print(f"风格迁移完成: {output_path}")

9.3 基于频谱的风格迁移

import torch
import torchaudio
import numpy as np

class SpectralStyleTransfer:
    """基于频谱的风格迁移(无需训练)"""
    
    def __init__(self, n_fft=2048, hop_length=512):
        self.n_fft = n_fft
        self.hop_length = hop_length
    
    def extract_spectrogram(self, audio):
        """提取梅尔频谱"""
        transform = torchaudio.transforms.MelSpectrogram(
            sample_rate=22050,
            n_fft=self.n_fft,
            hop_length=self.hop_length,
            n_mels=128
        )
        return transform(audio)
    
    def gram_matrix(self, spectrogram):
        """计算 Gram 矩阵(风格表示)"""
        b, c, h, w = spectrogram.shape
        features = spectrogram.view(b, c, -1)
        gram = torch.bmm(features, features.transpose(1, 2))
        return gram / (c * h * w)
    
    def transfer(self, content_path, style_path, output_path, 
                 alpha=0.5, num_steps=300):
        """
        执行基于优化的风格迁移
        
        Args:
            content_path: 内容音频路径
            style_path: 风格音频路径
            output_path: 输出路径
            alpha: 内容/风格平衡系数(0-1,越大越偏向内容)
            num_steps: 优化步数
        """
        device = "cuda" if torch.cuda.is_available() else "cpu"
        
        # 加载音频
        content_audio, sr = torchaudio.load(content_path)
        style_audio, _ = torchaudio.load(style_path)
        
        # 统一处理
        target_sr = 22050
        content_audio = torchaudio.transforms.Resample(sr, target_sr)(content_audio)
        style_audio = torchaudio.transforms.Resample(sr, target_sr)(style_audio)
        
        if content_audio.shape[0] > 1:
            content_audio = content_audio.mean(dim=0, keepdim=True)
        if style_audio.shape[0] > 1:
            style_audio = style_audio.mean(dim=0, keepdim=True)
        
        # 提取频谱
        content_spec = self.extract_spectrogram(content_audio).to(device)
        style_spec = self.extract_spectrogram(style_audio).to(device)
        
        # 初始化目标频谱(从内容频谱开始)
        target_spec = content_spec.clone().requires_grad_(True)
        
        # 优化器
        optimizer = torch.optim.Adam([target_spec], lr=0.01)
        
        # 风格目标 Gram 矩阵
        style_gram = self.gram_matrix(style_spec.unsqueeze(0))
        
        for step in range(num_steps):
            optimizer.zero_grad()
            
            # 内容损失
            content_loss = torch.mean((target_spec - content_spec) ** 2)
            
            # 风格损失
            target_gram = self.gram_matrix(target_spec.unsqueeze(0))
            style_loss = torch.mean((target_gram - style_gram) ** 2)
            
            # 总损失
            total_loss = alpha * content_loss + (1 - alpha) * style_loss
            
            total_loss.backward()
            optimizer.step()
            
            if step % 50 == 0:
                print(f"Step {step}, Loss: {total_loss.item():.4f} "
                      f"(Content: {content_loss.item():.4f}, "
                      f"Style: {style_loss.item():.4f})")
        
        # 从频谱重建音频(Griffin-Lim 算法)
        output_audio = self.spectrogram_to_audio(
            target_spec.detach(), target_sr
        )
        
        torchaudio.save(output_path, output_audio.cpu(), target_sr)
        print(f"风格迁移完成: {output_path}")
    
    def spectrogram_to_audio(self, spectrogram, sample_rate):
        """从梅尔频谱重建音频"""
        # 反梅尔变换
        inverse_mel = torchaudio.transforms.InverseMelScale(
            n_stft=self.n_fft // 2 + 1,
            n_mels=128,
            sample_rate=sample_rate
        )
        
        # Griffin-Lim 重建
        griffin_lim = torchaudio.transforms.GriffinLim(
            n_fft=self.n_fft,
            hop_length=self.hop_length
        )
        
        spec = inverse_mel(spectrogram)
        audio = griffin_lim(spec)
        
        return audio

第十章:版权与商业化

10.1 AI 生成音乐的版权现状

AI 生成音乐的版权问题是一个快速发展的法律领域。以下是截至 2025 年的关键要点:

基本原则:

  1. 纯 AI 生成:多数国家认为纯 AI 生成的内容不受版权保护,因为缺乏人类创作
  2. 人类引导的 AI 创作:如果人类在创作过程中有实质性贡献(如提示词设计、选择、编辑),可能获得版权保护
  3. 平台条款:不同平台对生成内容的版权归属有不同的条款

10.2 各平台版权政策对比

PLATFORM_COPYRIGHT = {
    "Suno AI": {
        "免费用户": "Suno 保留版权,仅限非商业用途",
        "付费用户": "用户拥有版权,可用于商业用途",
        "商业授权": "Pro 计划及以上包含商业使用权",
        "注意事项": "生成内容不能模仿特定艺术家的声音"
    },
    "Udio": {
        "免费用户": "有限的使用权,非商业用途",
        "付费用户": "用户拥有生成内容的权利",
        "商业授权": "付费计划包含商业使用",
        "注意事项": "需遵守使用条款"
    },
    "Stable Audio": {
        "开源版本": "遵循 Stability AI 的许可协议",
        "商业使用": "需要检查具体模型的许可条款",
        "本地部署": "生成内容的权利归使用者",
        "注意事项": "非商业研究用途的许可可能限制商业使用"
    },
    "MusicGen": {
        "许可协议": "CC-BY-NC 4.0(非商业用途)",
        "商业使用": "需要联系 Meta 获取商业许可",
        "注意事项": "输出内容的版权归属尚不明确"
    }
}

10.3 商业化最佳实践

class MusicCommercialization:
    """AI 音乐商业化指南"""
    
    BEST_PRACTICES = {
        "版权保护策略": [
            "记录完整的创作过程(提示词、参数、选择过程)",
            "保存所有中间版本和最终版本",
            "对 AI 生成内容进行人类编辑和改编",
            "注册版权(如果当地法律支持)",
            "使用付费商业授权的平台"
        ],
        
        "商业应用场景": {
            "播客配乐": {
                "风险等级": "低",
                "建议": "使用付费平台生成,保留创作记录"
            },
            "广告音乐": {
                "风险等级": "中",
                "建议": "获取明确的商业授权,考虑购买版权保险"
            },
            "影视配乐": {
                "风险等级": "中",
                "建议": "AI 生成作为起点,人类作曲家进行改编"
            },
            "音乐发行": {
                "风险等级": "高",
                "建议": "确保完全的版权归属,咨询法律专业人士"
            },
            "游戏音效": {
                "风险等级": "低",
                "建议": "音效通常风险较低,保留生成记录"
            }
        },
        
        "收入模式": [
            "授权许可:按使用场景收取授权费",
            "定制服务:为客户提供 AI 辅助的定制音乐",
            "音乐库:建立 AI 生成的音乐素材库",
            "教育内容:教授 AI 音乐创作技术",
            "工具开发:开发 AI 音乐创作工具和插件"
        ]
    }
    
    def generate_license(self, platform, usage_type, duration):
        """生成简单的授权协议模板"""
        template = f"""
AI 生成音乐授权协议

授权方:[授权方名称]
被授权方:[被授权方名称]
生成平台:{platform}
使用类型:{usage_type}
授权期限:{duration}

条款:
1. 授权方保证拥有通过 {platform} 生成的音乐的使用权
2. 被授权方获得 {usage_type} 的非独占使用权
3. 授权期限为 {duration}
4. 被授权方不得将音乐用于 [禁止用途]
5. 本协议受 [适用法律] 管辖

签署日期:____年____月____日
"""
        return template

10.4 避免侵权的实用建议

✅ 推荐做法:
1. 使用明确授予商业使用权的付费平台
2. 对 AI 生成内容进行实质性的人类创作改编
3. 保留完整的创作过程记录
4. 避免模仿特定艺术家的声音或风格
5. 定期关注 AI 版权法律的最新发展
6. 在商业使用前咨询法律专业人士

❌ 避免做法:
1. 直接将 AI 生成内容署名为人类创作
2. 使用免费版本进行商业项目
3. 模仿受版权保护的特定歌曲
4. 忽视平台的使用条款
5. 声称 AI 生成内容具有人类版权

第十一章:实战项目一 — AI 音乐创作工作流

11.1 项目概述

本项目将构建一个完整的 AI 音乐创作工作流,从创意灵感到最终作品,整合多个 AI 工具。

11.2 系统架构

"""
AI 音乐创作工作流系统

架构:
1. 创意输入层 - 接收用户的创意描述
2. AI 生成层 - 使用多个模型生成音乐
3. 后期处理层 - 音频处理和混音
4. 输出层 - 导出最终作品
"""

import os
import json
import torch
import torchaudio
from datetime import datetime
from pathlib import Path

class AIMusicWorkflow:
    """AI 音乐创作工作流"""
    
    def __init__(self, workspace_dir="./music_projects"):
        self.workspace = Path(workspace_dir)
        self.workspace.mkdir(parents=True, exist_ok=True)
        self.project_history = []
    
    def create_project(self, name, description):
        """创建新项目"""
        project_dir = self.workspace / f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
        project_dir.mkdir(exist_ok=True)
        
        # 创建项目结构
        (project_dir / "raw").mkdir()        # 原始生成
        (project_dir / "processed").mkdir()  # 处理后
        (project_dir / "final").mkdir()      # 最终版本
        (project_dir / "metadata").mkdir()   # 元数据
        
        project_info = {
            "name": name,
            "description": description,
            "created_at": datetime.now().isoformat(),
            "status": "created",
            "generations": []
        }
        
        with open(project_dir / "project.json", "w") as f:
            json.dump(project_info, f, indent=2, ensure_ascii=False)
        
        print(f"项目已创建: {project_dir}")
        return project_dir
    
    def generate_ideas(self, theme, num_ideas=5):
        """
        使用 LLM 生成音乐创意
        
        Args:
            theme: 主题描述
            num_ideas: 生成创意数量
        
        Returns:
            list: 创意列表
        """
        # 这里可以集成任何 LLM API
        # 示例使用本地模板生成
        ideas = []
        
        style_templates = [
            {"style": "ambient electronic", "mood": "contemplative", "tempo": "slow"},
            {"style": "acoustic folk", "mood": "warm and intimate", "tempo": "moderate"},
            {"style": "orchestral cinematic", "mood": "epic and dramatic", "tempo": "variable"},
            {"style": "jazz fusion", "mood": "sophisticated", "tempo": "moderate"},
            {"style": "lo-fi hip hop", "mood": "chill and relaxed", "tempo": "slow"},
            {"style": "indie rock", "mood": "energetic and raw", "tempo": "fast"},
            {"style": "classical piano", "mood": "elegant and refined", "tempo": "moderate"},
            {"style": "world music fusion", "mood": "exotic and vibrant", "tempo": "moderate"}
        ]
        
        for i in range(min(num_ideas, len(style_templates))):
            template = style_templates[i]
            idea = {
                "id": i + 1,
                "theme": theme,
                "style": template["style"],
                "mood": template["mood"],
                "tempo": template["tempo"],
                "prompt": f"{template['style']} music, {template['mood']} feeling, "
                         f"about {theme}, {template['tempo']} tempo"
            }
            ideas.append(idea)
        
        return ideas
    
    def generate_demos(self, ideas, model_size="small", duration=15.0):
        """
        批量生成演示版本
        
        Args:
            ideas: 创意列表
            model_size: 模型大小
            duration: 每段时长
        """
        from audiocraft.models import MusicGen
        
        model = MusicGen.get_pretrained(f"facebook/musicgen-{model_size}")
        model.set_generation_params(duration=duration)
        
        demos = []
        for idea in ideas:
            print(f"生成 Demo #{idea['id']}: {idea['style']}")
            wav = model.generate([idea["prompt"]])
            
            demo_path = self.workspace / "demos" / f"demo_{idea['id']}.wav"
            demo_path.parent.mkdir(exist_ok=True)
            
            torchaudio.save(str(demo_path), wav[0].cpu(), model.sample_rate)
            
            demo_info = {
                **idea,
                "audio_path": str(demo_path),
                "sample_rate": model.sample_rate,
                "duration": duration
            }
            demos.append(demo_info)
            print(f"  已保存: {demo_path}")
        
        return demos
    
    def select_and_refine(self, demos, selected_ids):
        """
        选择并精化选定的演示
        
        Args:
            demos: 演示列表
            selected_ids: 选定的 ID 列表
        """
        selected = [d for d in demos if d["id"] in selected_ids]
        
        refined = []
        for demo in selected:
            # 加载音频
            audio, sr = torchaudio.load(demo["audio_path"])
            
            # 基础后期处理
            audio = self._basic_processing(audio, sr)
            
            # 保存处理后版本
            refined_path = demo["audio_path"].replace("demos", "refined")
            os.makedirs(os.path.dirname(refined_path), exist_ok=True)
            torchaudio.save(refined_path, audio, sr)
            
            demo["refined_path"] = refined_path
            refined.append(demo)
        
        return refined
    
    def _basic_processing(self, audio, sample_rate):
        """基础音频处理"""
        # 1. 标准化音量
        audio = audio / audio.abs().max() * 0.9
        
        # 2. 简单的淡入淡出
        fade_samples = int(sample_rate * 0.5)  # 0.5秒
        fade_in = torch.linspace(0, 1, fade_samples)
        fade_out = torch.linspace(1, 0, fade_samples)
        
        audio[:, :fade_samples] *= fade_in
        audio[:, -fade_samples:] *= fade_out
        
        return audio
    
    def mix_tracks(self, tracks, mix_config=None):
        """
        混合多个音轨
        
        Args:
            tracks: 音轨列表
            mix_config: 混音配置
        """
        if mix_config is None:
            mix_config = {
                "crossfade_duration": 2.0,
                "normalize": True,
                "target_loudness": -14.0  # LUFS
            }
        
        # 加载所有音轨
        loaded_tracks = []
        for track in tracks:
            audio, sr = torchaudio.load(track["refined_path"])
            loaded_tracks.append((audio, sr))
        
        # 确保所有音轨采样率一致
        target_sr = loaded_tracks[0][1]
        resampled = []
        for audio, sr in loaded_tracks:
            if sr != target_sr:
                resampler = torchaudio.transforms.Resample(sr, target_sr)
                audio = resampler(audio)
            resampled.append(audio)
        
        # 拼接(带交叉淡化)
        crossfade_samples = int(target_sr * mix_config["crossfade_duration"])
        result = resampled[0]
        
        for i in range(1, len(resampled)):
            fade_out = torch.linspace(1, 0, crossfade_samples)
            fade_in = torch.linspace(0, 1, crossfade_samples)
            
            result_end = result[:, -crossfade_samples:] * fade_out
            next_start = resampled[i][:, :crossfade_samples] * fade_in
            
            crossfaded = result_end + next_start
            result = torch.cat([
                result[:, :-crossfade_samples],
                crossfaded,
                resampled[i][:, crossfade_samples:]
            ], dim=-1)
        
        # 最终标准化
        if mix_config["normalize"]:
            result = result / result.abs().max() * 0.95
        
        return result, target_sr
    
    def export_project(self, project_dir, formats=["wav", "mp3"]):
        """导出项目"""
        final_dir = project_dir / "final"
        
        for fmt in formats:
            export_dir = project_dir / "export" / fmt
            export_dir.mkdir(parents=True, exist_ok=True)
            
            for audio_file in final_dir.glob("*.wav"):
                if fmt == "wav":
                    # 直接复制
                    import shutil
                    shutil.copy(audio_file, export_dir / audio_file.name)
                elif fmt == "mp3":
                    # 转换为 MP3(需要 ffmpeg)
                    output_path = export_dir / audio_file.with_suffix(".mp3").name
                    os.system(f"ffmpeg -i {audio_file} -b:a 320k {output_path}")
        
        print(f"项目已导出到: {project_dir / 'export'}")


# 完整工作流示例
def run_complete_workflow():
    """运行完整的音乐创作工作流"""
    
    workflow = AIMusicWorkflow("./my_music_projects")
    
    # 步骤 1:创建项目
    project_dir = workflow.create_project(
        name="夏日回忆",
        description="关于夏天回忆的音乐专辑"
    )
    
    # 步骤 2:生成创意
    ideas = workflow.generate_ideas("夏日海边的回忆", num_ideas=5)
    print("\n生成的创意:")
    for idea in ideas:
        print(f"  #{idea['id']}: {idea['style']} - {idea['mood']}")
    
    # 步骤 3:生成演示
    demos = workflow.generate_demos(ideas, model_size="small", duration=20.0)
    
    # 步骤 4:选择最佳创意(模拟用户选择)
    selected_ids = [1, 3, 5]  # 用户选择第 1、3、5 个
    refined = workflow.select_and_refine(demos, selected_ids)
    
    # 步骤 5:混音
    mixed_audio, sr = workflow.mix_tracks(refined)
    
    # 步骤 6:保存最终版本
    final_path = project_dir / "final" / "summer_memories_album.wav"
    final_path.parent.mkdir(exist_ok=True)
    torchaudio.save(str(final_path), mixed_audio, sr)
    print(f"\n最终作品已保存: {final_path}")
    
    # 步骤 7:导出
    workflow.export_project(project_dir, formats=["wav", "mp3"])
    
    print("\n工作流完成!")


if __name__ == "__main__":
    run_complete_workflow()

第十二章:实战项目二 — 自动化配乐系统

12.1 项目概述

本项目将构建一个自动化配乐系统,能够根据输入的文本内容、视频或场景描述,自动生成匹配的背景音乐。

12.2 完整系统实现

"""
自动化配乐系统

功能:
1. 文本情感分析 → 音乐参数推荐
2. 场景识别 → 音乐风格匹配
3. 自动音乐生成 → 后期处理
4. 与视频/播客自动混合
"""

import os
import json
import torch
import torchaudio
import numpy as np
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass, asdict
from typing import List, Optional, Dict
from enum import Enum


class SceneType(Enum):
    """场景类型枚举"""
    ACTION = "action"
    ROMANCE = "romance"
    HORROR = "horror"
    COMEDY = "comedy"
    DRAMA = "drama"
    DOCUMENTARY = "documentary"
    NATURE = "nature"
    TECHNOLOGY = "technology"
    MOTIVATION = "motivation"
    SADNESS = "sadness"


@dataclass
class MusicParams:
    """音乐参数数据类"""
    genre: str
    tempo: str
    key: str
    mood: str
    instruments: List[str]
    energy: float  # 0-1
    duration: float
    description: str


@dataclass
class SceneSegment:
    """场景片段数据类"""
    start_time: float
    end_time: float
    scene_type: SceneType
    description: str
    intensity: float  # 0-1


# 场景到音乐参数的映射配置
SCENE_MUSIC_MAPPING = {
    SceneType.ACTION: MusicParams(
        genre="electronic/cinematic",
        tempo="fast 140-160 BPM",
        key="minor",
        mood="intense and exciting",
        instruments=["synth bass", "drums", "electric guitar", "orchestral brass"],
        energy=0.9,
        duration=0,
        description="High-energy action music with driving beats"
    ),
    SceneType.ROMANCE: MusicParams(
        genre="classical/pop ballad",
        tempo="slow 60-80 BPM",
        key="major",
        mood="romantic and tender",
        instruments=["piano", "strings", "soft guitar", "flute"],
        energy=0.3,
        duration=0,
        description="Gentle romantic music with warm melodies"
    ),
    SceneType.HORROR: MusicParams(
        genre="ambient/experimental",
        tempo="variable 50-90 BPM",
        key="minor",
        mood="tense and eerie",
        instruments=["low strings", "synthesizer", "dissonant piano", "sub bass"],
        energy=0.6,
        duration=0,
        description="Dark atmospheric music with unsettling tones"
    ),
    SceneType.COMEDY: MusicParams(
        genre="jazz/pop",
        tempo="moderate 110-130 BPM",
        key="major",
        mood="playful and lighthearted",
        instruments=["ukulele", "xylophone", "light percussion", "bass"],
        energy=0.5,
        duration=0,
        description="Fun and quirky music with playful rhythms"
    ),
    SceneType.DRAMA: MusicParams(
        genre="orchestral",
        tempo="moderate 80-100 BPM",
        key="minor",
        mood="emotional and powerful",
        instruments=["full orchestra", "piano", "cello", "violin"],
        energy=0.7,
        duration=0,
        description="Dramatic orchestral music with emotional depth"
    ),
    SceneType.DOCUMENTARY: MusicParams(
        genre="ambient/contemporary",
        tempo="moderate 90-110 BPM",
        key="major",
        mood="informative and engaging",
        instruments=["piano", "light strings", "ambient pads", "guitar"],
        energy=0.4,
        duration=0,
        description="Subtle background music that supports narration"
    ),
    SceneType.NATURE: MusicParams(
        genre="ambient/world",
        tempo="slow 60-80 BPM",
        key="major",
        mood="peaceful and natural",
        instruments=["acoustic guitar", "flute", "nature sounds", "soft strings"],
        energy=0.2,
        duration=0,
        description="Calming nature-inspired ambient music"
    ),
    SceneType.TECHNOLOGY: MusicParams(
        genre="electronic/synthwave",
        tempo="moderate 110-128 BPM",
        key="minor",
        mood="futuristic and innovative",
        instruments=["synthesizer", "electronic drums", "bass synth", "arpeggios"],
        energy=0.6,
        duration=0,
        description="Modern electronic music with tech vibes"
    ),
    SceneType.MOTIVATION: MusicParams(
        genre="pop/rock",
        tempo="fast 120-140 BPM",
        key="major",
        mood="uplifting and empowering",
        instruments=["electric guitar", "drums", "bass", "synth pads"],
        energy=0.8,
        duration=0,
        description="Inspiring music that builds momentum"
    ),
    SceneType.SADNESS: MusicParams(
        genre="classical/ambient",
        tempo="slow 50-70 BPM",
        key="minor",
        mood="melancholic and reflective",
        instruments=["piano", "cello", "soft strings", "rain sounds"],
        energy=0.2,
        duration=0,
        description="Sorrowful music with gentle, mournful melodies"
    )
}


class AutoScoringSystem:
    """自动化配乐系统"""
    
    def __init__(self, output_dir="./scoring_output"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        
        # 初始化模型(延迟加载)
        self._music_model = None
        self._emotion_analyzer = None
    
    @property
    def music_model(self):
        """延迟加载音乐生成模型"""
        if self._music_model is None:
            from audiocraft.models import MusicGen
            print("正在加载 MusicGen 模型...")
            self._music_model = MusicGen.get_pretrained("facebook/musicgen-medium")
        return self._music_model
    
    def analyze_text(self, text: str) -> Dict:
        """
        分析文本内容,提取情感和场景信息
        
        Args:
            text: 输入文本
        
        Returns:
            dict: 分析结果
        """
        # 简单的关键词匹配(生产环境应使用 NLP 模型)
        keyword_mapping = {
            SceneType.ACTION: ["战斗", "追逐", "爆炸", "动作", "紧张", "快速", "激烈"],
            SceneType.ROMANCE: ["爱情", "浪漫", "温柔", "拥抱", "亲吻", "心跳", "甜蜜"],
            SceneType.HORROR: ["恐怖", "黑暗", "恐惧", "鬼", "阴影", "尖叫", "死亡"],
            SceneType.COMEDY: ["搞笑", "幽默", "笑话", "欢乐", "有趣", "滑稽", "哈哈"],
            SceneType.DRAMA: ["悲伤", "感动", "泪水", "命运", "挣扎", "勇气", "牺牲"],
            SceneType.DOCUMENTARY: ["历史", "科学", "探索", "发现", "研究", "事实", "纪录"],
            SceneType.NATURE: ["自然", "森林", "海洋", "山川", "花", "鸟", "风", "雨"],
            SceneType.TECHNOLOGY: ["科技", "未来", "AI", "数字", "创新", "智能", "数据"],
            SceneType.MOTIVATION: ["梦想", "奋斗", "成功", "坚持", "突破", "挑战", "加油"],
            SceneType.SADNESS: ["离别", "失去", "孤独", "回忆", "思念", "眼泪", "告别"]
        }
        
        scores = {}
        for scene_type, keywords in keyword_mapping.items():
            score = sum(1 for kw in keywords if kw in text)
            if score > 0:
                scores[scene_type] = score
        
        if not scores:
            dominant_scene = SceneType.DRAMA
        else:
            dominant_scene = max(scores, key=scores.get)
        
        return {
            "text": text[:100] + "..." if len(text) > 100 else text,
            "dominant_scene": dominant_scene,
            "scene_scores": {k.value: v for k, v in scores.items()},
            "music_params": SCENE_MUSIC_MAPPING[dominant_scene]
        }
    
    def analyze_scenes(self, scenes: List[SceneSegment]) -> List[Dict]:
        """
        分析多个场景
        
        Args:
            scenes: 场景列表
        
        Returns:
            list: 每个场景的音乐参数
        """
        results = []
        for scene in scenes:
            params = SCENE_MUSIC_MAPPING[scene.scene_type]
            params.duration = scene.end_time - scene.start_time
            
            # 根据强度调整能量
            params.energy = min(1.0, params.energy * scene.intensity)
            
            results.append({
                "scene": asdict(scene),
                "music_params": asdict(params)
            })
        
        return results
    
    def generate_music_for_scene(self, scene: SceneSegment, 
                                   output_name: str) -> str:
        """
        为单个场景生成音乐
        
        Args:
            scene: 场景信息
            output_name: 输出文件名
        
        Returns:
            str: 生成的音频文件路径
        """
        params = SCENE_MUSIC_MAPPING[scene.scene_type]
        duration = scene.end_time - scene.start_time
        
        # 构建提示词
        prompt = (
            f"{params.genre} music, {params.mood}, {params.tempo}, "
            f"{', '.join(params.instruments[:3])}, "
            f"{params.description}"
        )
        
        # 添加场景特定描述
        if scene.description:
            prompt += f", {scene.description}"
        
        print(f"  生成场景音乐: {scene.scene_type.value} ({duration:.1f}秒)")
        print(f"  提示词: {prompt[:80]}...")
        
        # 生成音乐
        self.music_model.set_generation_params(duration=duration)
        wav = self.music_model.generate([prompt])
        
        # 保存
        output_path = self.output_dir / f"{output_name}.wav"
        torchaudio.save(str(output_path), wav[0].cpu(), self.music_model.sample_rate)
        
        return str(output_path)
    
    def generate_full_soundtrack(self, scenes: List[SceneSegment], 
                                   project_name: str) -> str:
        """
        为完整项目生成配乐
        
        Args:
            scenes: 场景列表
            project_name: 项目名称
        
        Returns:
            str: 最终配乐文件路径
        """
        print(f"\n{'='*50}")
        print(f"开始生成配乐: {project_name}")
        print(f"场景数量: {len(scenes)}")
        print(f"{'='*50}\n")
        
        # 为每个场景生成音乐
        scene_audio_paths = []
        for i, scene in enumerate(scenes):
            print(f"\n场景 {i+1}/{len(scenes)}: {scene.scene_type.value}")
            audio_path = self.generate_music_for_scene(
                scene, f"{project_name}_scene_{i+1:02d}"
            )
            scene_audio_paths.append(audio_path)
        
        # 拼接所有场景音乐
        print(f"\n拼接 {len(scene_audio_paths)} 个场景...")
        final_audio = self._concatenate_scenes(scene_audio_paths, scenes)
        
        # 保存最终配乐
        final_path = self.output_dir / f"{project_name}_soundtrack.wav"
        torchaudio.save(str(final_path), final_audio, self.music_model.sample_rate)
        
        # 生成报告
        report = self._generate_report(scenes, scene_audio_paths, str(final_path))
        report_path = self.output_dir / f"{project_name}_report.json"
        with open(report_path, "w") as f:
            json.dump(report, f, indent=2, ensure_ascii=False)
        
        print(f"\n配乐生成完成: {final_path}")
        print(f"生成报告: {report_path}")
        
        return str(final_path)
    
    def _concatenate_scenes(self, audio_paths: List[str], 
                              scenes: List[SceneSegment]) -> torch.Tensor:
        """拼接场景音乐(带交叉淡化)"""
        crossfade_duration = 2.0  # 交叉淡化时长
        crossfade_samples = int(self.music_model.sample_rate * crossfade_duration)
        
        all_audio = []
        for path in audio_paths:
            audio, _ = torchaudio.load(path)
            all_audio.append(audio)
        
        if len(all_audio) == 1:
            return all_audio[0]
        
        result = all_audio[0]
        for i in range(1, len(all_audio)):
            # 确保有足够的样本用于交叉淡化
            if result.shape[-1] < crossfade_samples or \
               all_audio[i].shape[-1] < crossfade_samples:
                # 直接拼接
                result = torch.cat([result, all_audio[i]], dim=-1)
                continue
            
            fade_out = torch.linspace(1, 0, crossfade_samples)
            fade_in = torch.linspace(0, 1, crossfade_samples)
            
            result_end = result[:, -crossfade_samples:] * fade_out
            next_start = all_audio[i][:, :crossfade_samples] * fade_in
            
            crossfaded = result_end + next_start
            result = torch.cat([
                result[:, :-crossfade_samples],
                crossfaded,
                all_audio[i][:, crossfade_samples:]
            ], dim=-1)
        
        # 最终标准化
        result = result / result.abs().max() * 0.95
        
        return result
    
    def _generate_report(self, scenes, audio_paths, final_path):
        """生成配乐报告"""
        return {
            "project": "Auto Scoring System",
            "generated_at": datetime.now().isoformat(),
            "total_scenes": len(scenes),
            "total_duration": sum(s.end_time - s.start_time for s in scenes),
            "scenes": [
                {
                    "index": i,
                    "type": scene.scene_type.value,
                    "start": scene.start_time,
                    "end": scene.end_time,
                    "duration": scene.end_time - scene.start_time,
                    "description": scene.description,
                    "audio_file": audio_paths[i]
                }
                for i, scene in enumerate(scenes)
            ],
            "output_file": final_path,
            "sample_rate": self.music_model.sample_rate
        }


# 使用示例
def demo_auto_scoring():
    """演示自动配乐系统"""
    
    system = AutoScoringSystem("./demo_scoring")
    
    # 定义场景序列(模拟一个短片的场景)
    scenes = [
        SceneSegment(
            start_time=0, end_time=15,
            scene_type=SceneType.NATURE,
            description="清晨的海边,阳光洒在波浪上",
            intensity=0.3
        ),
        SceneSegment(
            start_time=15, end_time=30,
            scene_type=SceneType.ROMANCE,
            description="两人在沙滩上漫步,甜蜜对话",
            intensity=0.5
        ),
        SceneSegment(
            start_time=30, end_time=45,
            scene_type=SceneType.DRAMA,
            description="突然的暴风雨,两人分离",
            intensity=0.8
        ),
        SceneSegment(
            start_time=45, end_time=60,
            scene_type=SceneType.SADNESS,
            description="独自在雨中,回忆过去的美好",
            intensity=0.6
        ),
        SceneSegment(
            start_time=60, end_time=80,
            scene_type=SceneType.MOTIVATION,
            description="重新振作,追逐梦想",
            intensity=0.9
        ),
        SceneSegment(
            start_time=80, end_time=100,
            scene_type=SceneType.ROMANCE,
            description="重逢,拥抱,温馨结局",
            intensity=0.7
        )
    ]
    
    # 生成完整配乐
    final_path = system.generate_full_soundtrack(scenes, "海边故事")
    print(f"\n最终配乐: {final_path}")


if __name__ == "__main__":
    demo_auto_scoring()

第十三章:常见问题与解决方案

13.1 模型加载与显存问题

Q1: 加载模型时出现 CUDA 显存不足错误

# 解决方案 1:使用 CPU 模式
import os
os.environ["CUDA_VISIBLE_DEVICES"] = ""

# 解决方案 2:使用更小的模型
model = MusicGen.get_pretrained("facebook/musicgen-small")  # 而非 medium/large

# 解决方案 3:使用半精度浮点数
model = model.to(torch.float16)

# 解决方案 4:使用模型量化
from audiocraft.models import MusicGen
model = MusicGen.get_pretrained("facebook/musicgen-small")
# 手动量化
model.model = torch.quantization.quantize_dynamic(
    model.model, {torch.nn.Linear}, dtype=torch.qint8
)

Q2: 生成速度太慢怎么办

# 解决方案 1:减少生成时长
model.set_generation_params(duration=8.0)  # 而非 30 秒

# 解决方案 2:减少扩散步数(适用于 Stable Audio)
output = generate_diffusion_cond(model, steps=50)  # 而非 100-200

# 解决方案 3:使用半精度
with torch.cuda.amp.autocast():
    wav = model.generate(["描述"])

# 解决方案 4:批量生成(如果有多个 GPU)
# 使用 DataParallel 或 DistributedDataParallel

13.2 音频质量问题

Q3: 生成的音乐有杂音或失真

import torchaudio

def clean_audio(input_path, output_path):
    """清理音频杂音"""
    audio, sr = torchaudio.load(input_path)
    
    # 1. 去除直流偏移
    audio = audio - audio.mean()
    
    # 2. 限幅处理(防止削波)
    audio = torch.clamp(audio, -0.99, 0.99)
    
    # 3. 简单的低通滤波(去除高频噪音)
    # 使用 torchaudio 的滤波器
    lowpass = torchaudio.transforms.LowpassWaveform(
        sample_rate=sr, cutoff_freq=15000
    )
    audio = lowpass(audio)
    
    # 4. 标准化音量
    audio = audio / audio.abs().max() * 0.95
    
    torchaudio.save(output_path, audio, sr)
    print(f"清理后的音频已保存: {output_path}")

Q4: 生成的音乐风格不符预期

# 解决方案:更精确的提示词工程

# ❌ 模糊的提示词
bad_prompt = "好听的音乐"

# ✅ 精确的提示词
good_prompt = """
Acoustic folk music, warm and intimate feeling, 
moderate tempo around 100 BPM, 
fingerstyle acoustic guitar as main instrument, 
light percussion with cajón, 
major key, bright and hopeful mood, 
suitable for a coffee shop atmosphere
"""

# 提示词优化技巧:
# 1. 指定具体风格而非模糊描述
# 2. 包含节奏信息(BPM)
# 3. 列出主要乐器
# 4. 描述情感和氛围
# 5. 添加参考场景或用途

13.3 音频处理问题

Q5: 拼接的音乐片段之间有明显的接缝

def smooth_concatenate(audio_list, sample_rate, crossfade_ms=2000):
    """
    平滑拼接多个音频片段
    
    Args:
        audio_list: 音频张量列表
        sample_rate: 采样率
        crossfade_ms: 交叉淡化时长(毫秒)
    """
    crossfade_samples = int(sample_rate * crossfade_ms / 1000)
    
    result = audio_list[0]
    
    for i in range(1, len(audio_list)):
        current = audio_list[i]
        
        # 确保有足够的样本
        if result.shape[-1] < crossfade_samples:
            result = torch.cat([result, current], dim=-1)
            continue
        
        # 等功率交叉淡化
        fade_out = torch.cos(torch.linspace(0, torch.pi/2, crossfade_samples))
        fade_in = torch.sin(torch.linspace(0, torch.pi/2, crossfade_samples))
        
        # 应用淡化
        result_end = result[:, -crossfade_samples:] * fade_out
        next_start = current[:, :crossfade_samples] * fade_in
        
        # 拼接
        crossfaded = result_end + next_start
        result = torch.cat([
            result[:, :-crossfade_samples],
            crossfaded,
            current[:, crossfade_samples:]
        ], dim=-1)
    
    return result

Q6: 生成的音频长度不准确

def adjust_audio_length(audio, target_duration, sample_rate):
    """
    调整音频到目标时长
    
    Args:
        audio: 输入音频张量
        target_duration: 目标时长(秒)
        sample_rate: 采样率
    """
    target_samples = int(target_duration * sample_rate)
    current_samples = audio.shape[-1]
    
    if current_samples > target_samples:
        # 截取,并添加淡出
        audio = audio[:, :target_samples]
        fade_out_samples = int(sample_rate * 0.5)  # 0.5秒淡出
        fade_out = torch.linspace(1, 0, fade_out_samples)
        audio[:, -fade_out_samples:] *= fade_out
    elif current_samples < target_samples:
        # 填充静音,并添加淡入
        padding = target_samples - current_samples
        silence = torch.zeros(audio.shape[0], padding)
        
        # 在原音频末尾淡出
        fade_out_samples = int(sample_rate * 0.3)
        audio[:, -fade_out_samples:] *= torch.linspace(1, 0, fade_out_samples)
        
        audio = torch.cat([audio, silence], dim=-1)
    
    return audio

13.4 部署与集成问题

Q7: 如何将 AI 音乐生成集成到 Web 应用

from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
import uuid
import asyncio

app = FastAPI()

class GenerateRequest(BaseModel):
    prompt: str
    duration: float = 10.0
    style: str = "general"

class GenerateResponse(BaseModel):
    task_id: str
    status: str
    message: str

# 任务存储
tasks = {}

@app.post("/generate", response_model=GenerateResponse)
async def generate_music(request: GenerateRequest, 
                          background_tasks: BackgroundTasks):
    """提交音乐生成任务"""
    task_id = str(uuid.uuid4())
    tasks[task_id] = {"status": "pending", "audio_path": None}
    
    background_tasks.add_task(
        run_generation, task_id, request.prompt, request.duration
    )
    
    return GenerateResponse(
        task_id=task_id,
        status="pending",
        message="音乐生成任务已提交"
    )

@app.get("/status/{task_id}")
async def get_status(task_id: str):
    """查询任务状态"""
    if task_id not in tasks:
        return {"error": "任务不存在"}
    return tasks[task_id]

async def run_generation(task_id, prompt, duration):
    """后台执行生成任务"""
    try:
        tasks[task_id]["status"] = "processing"
        
        # 这里调用音乐生成逻辑
        # audio_path = generate_audio(prompt, duration)
        
        # 模拟生成
        await asyncio.sleep(5)
        audio_path = f"output/{task_id}.wav"
        
        tasks[task_id]["status"] = "completed"
        tasks[task_id]["audio_path"] = audio_path
    except Exception as e:
        tasks[task_id]["status"] = "failed"
        tasks[task_id]["error"] = str(e)


# 运行: uvicorn main:app --reload

Q8: 如何在生产环境中优化性能

# 性能优化策略

# 1. 模型缓存(避免重复加载)
_model_cache = {}

def get_model(model_name):
    if model_name not in _model_cache:
        from audiocraft.models import MusicGen
        _model_cache[model_name] = MusicGen.get_pretrained(model_name)
    return _model_cache[model_name]

# 2. 请求队列管理
from collections import deque
import threading

class GenerationQueue:
    def __init__(self, max_concurrent=2):
        self.queue = deque()
        self.semaphore = threading.Semaphore(max_concurrent)
    
    def add_task(self, task):
        self.queue.append(task)
        self._process_next()
    
    def _process_next(self):
        if self.queue and self.semaphore.acquire(blocking=False):
            task = self.queue.popleft()
            threading.Thread(target=self._run_task, args=(task,)).start()
    
    def _run_task(self, task):
        try:
            task()
        finally:
            self.semaphore.release()
            self._process_next()

# 3. 音频缓存(相同提示词复用结果)
import hashlib

_cache = {}

def cached_generate(prompt, duration):
    cache_key = hashlib.md5(f"{prompt}_{duration}".encode()).hexdigest()
    
    if cache_key in _cache:
        return _cache[cache_key]
    
    result = generate_audio(prompt, duration)
    _cache[cache_key] = result
    return result

13.5 常见错误代码与解决

ERROR_SOLUTIONS = {
    "CUDA out of memory": {
        "原因": "GPU 显存不足",
        "解决": [
            "使用更小的模型(small 而非 large)",
            "减少生成时长",
            "使用半精度浮点 (float16)",
            "关闭其他占用显存的程序",
            "使用 CPU 模式(速度较慢)"
        ]
    },
    "Model not found": {
        "原因": "模型下载失败或路径错误",
        "解决": [
            "检查网络连接",
            "使用 Hugging Face 镜像源",
            "手动下载模型到指定目录",
            "检查模型名称是否正确"
        ]
    },
    "Audio format not supported": {
        "原因": "音频格式不兼容",
        "解决": [
            "使用 torchaudio 转换格式",
            "安装 ffmpeg 支持更多格式",
            "使用 WAV 格式作为中间格式"
        ]
    },
    "Sample rate mismatch": {
        "原因": "采样率不匹配",
        "解决": [
            "使用重采样统一采样率",
            "torchaudio.transforms.Resample(source_sr, target_sr)"
        ]
    }
}

附录:资源与工具汇总

A.1 开源模型资源

模型 来源 许可证 特点
MusicGen Meta CC-BY-NC 轻量、高效、多规格
Stable Audio Stability AI 开源 扩散模型、高质量
Bark Suno MIT 语音合成、多语言
AudioCraft Meta CC-BY-NC 音频生成框架
Riffusion Riffusion MIT 基于频谱的生成

A.2 在线平台

平台 网址 特点
Suno AI suno.com 文本生成完整歌曲
Udio udio.com 高质量音频生成
AIVA aiva.ai 古典音乐专精
Soundraw soundraw.io 定制化背景音乐
Mubert mubert.com 实时音乐生成

A.3 开发工具

# 常用 Python 库
REQUIREMENTS = """
torch>=2.0.0
torchaudio>=2.0.0
audiocraft>=1.0.0
stable-audio-tools>=0.1.0
transformers>=4.30.0
scipy>=1.10.0
soundfile>=0.12.0
librosa>=0.10.0
pydub>=0.25.1
moviepy>=1.0.3
fastapi>=0.100.0
uvicorn>=0.22.0
"""

# 安装命令
INSTALL_COMMAND = """
pip install torch torchaudio audiocraft
pip install stable-audio-tools
pip install transformers scipy soundfile
pip install librosa pydub moviepy
pip install fastapi uvicorn
"""

A.4 学习资源

推荐阅读:
1. "Music Generation with Deep Learning" - 学术论文综述
2. Hugging Face 音频处理教程
3. Meta AudioCraft 官方文档
4. Stability AI 技术博客

推荐社区:
1. GitHub - facebookresearch/audiocraft
2. Hugging Face Spaces - 音频生成 Demo
3. Reddit r/AIMusic
4. Discord - Suno AI 社区

A.5 环境配置快速参考

# 快速搭建 AI 音频开发环境

# 1. 创建虚拟环境
python -m venv ai-music-env
source ai-music-env/bin/activate  # Linux/Mac
# ai-music-env\Scripts\activate  # Windows

# 2. 安装 PyTorch(CUDA 11.8)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 3. 安装核心库
pip install audiocraft stable-audio-tools transformers
pip install scipy soundfile librosa pydub
pip install fastapi uvicorn python-multipart

# 4. 验证安装
python -c "import torch; print(f'PyTorch: {torch.__version__}, CUDA: {torch.cuda.is_available()}')"
python -c "from audiocraft.models import MusicGen; print('AudioCraft OK')"
python -c "from transformers import pipeline; print('Transformers OK')"

# 5. 下载模型(首次运行自动下载,或手动预下载)
python -c "
from audiocraft.models import MusicGen
model = MusicGen.get_pretrained('facebook/musicgen-small')
print('MusicGen small 模型下载完成')
"

总结

本教程从 AI 音乐生成的技术原理出发,系统介绍了 Suno AI、Udio、Stable Audio、MusicGen 等主流工具和平台的使用方法,深入讲解了 AI 音效生成、语音合成、BGM 自动配乐、音频风格迁移等进阶技术,并通过两个完整的实战项目展示了如何将这些技术整合为实际可用的系统。

关键要点回顾:

  1. 选择合适的工具:根据需求选择在线平台(快速出结果)或开源模型(可控性强)
  2. 提示词是关键:好的提示词 = 风格 + 情感 + 节奏 + 乐器 + 场景
  3. 后期处理不可少:AI 生成的音频通常需要标准化、淡入淡出、混音等处理
  4. 版权要重视:商业使用前务必确认授权条款
  5. 持续学习:AI 音频领域发展迅速,保持关注最新模型和技术

希望这份教程能帮助你开启 AI 音乐创作的旅程!


本教程最后更新:2025年 作者:AI 技术教程写作团队 许可:CC BY-SA 4.0

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录