AI 辅助和弦进行推荐:让非专业用户也能快速构建和声

AI 辅助和弦进行推荐:让非专业用户也能快速构建和声

一、懂和弦的写不出旋律,会写旋律的不懂和弦

这是独立音乐制作人最常见的困境。和弦进行(Chord Progression)是音乐的情绪骨架——同样的旋律搭不同的和弦,快乐能变悲伤。但非专业用户面对 7 个基本和弦×12 个调性的组合空间,往往无从下手。市面上大部分"和弦推荐"工具只是一个和弦库查询器,给出一个列表让用户自己试,这不是推荐,是扔了一本和弦字典。

真正的 AI 和弦推荐应该做到:用户输入一段旋律(MIDI 或哼唱),系统自动分析旋律中的音符分布和节拍重音,推荐合理的和弦进行,并给出至少 3 种风格化的变体(爵士、流行、古典)。我们的方案基于和弦功能理论 + Transformer 模型做上下文感知推荐。

二、从旋律到和弦的推理链路

graph TD A["输入: MIDI 旋律"] --> B["音符分析<br/>- 音高分布<br/>- 节拍重音<br/>- 乐句分段"] B --> C["调性检测<br/>Krumhansl-Schmuckler<br/>调性分析算法"] C --> D["和弦候选生成<br/>- 按调性筛选<br/>- 按功能分类<br/>(T/S/D)"] D --> E["Transformer 推荐<br/>- 上下文和弦关系建模<br/>- 风格向量注入"] E --> F["候选排序<br/>- 声部进行平滑度<br/>- 风格匹配度<br/>- 多样性评分"] F --> G["输出: 风格化变体<br/>- 爵士: 7/9/13 和弦<br/>- 流行: 三和弦为主<br/>- 古典: 严格声部进行"] style A fill:#4A90D9,color:#fff style C fill:#F5A623,color:#000 style E fill:#50B86C,color:#fff style G fill:#FF6B6B,color:#fff

关键设计:

  • 调性检测前置:在推荐和弦前,先确定旋律的调性。Krumhansl-Schmuckler 算法的基本思路是:将旋律中出现的音高频率分布与 24 个(12 大调 + 12 小调)标准调性模板做相关性计算,相关性最高的即为旋律的调性。
  • 和弦功能约束:和弦进行不是随机排列。功能和声理论中,和弦分 T(主功能、稳定)、S(下属功能、推进)、D(属功能、紧张→需解决),经典的 I-IV-V-I 进行就是 T-S-D-T。在生成候选时,用功能约束过滤掉不合理的和弦序列(如两个属和弦连续出现)。
  • 风格向量注入:在 Transformer 的输入层,将风格标签(jazz/pop/classical)作为额外的 Embedding 向量与和弦 Token 的 Embedding 相加,让模型学会在不同风格下生成不同的和弦排列。

三、生产级和弦推荐系统实现

调性检测模块

import numpy as np from typing import List, Tuple from dataclasses import dataclass @dataclass class KeyDetectionResult: key: str # "C" | "Am" | "F#" ... mode: str # "major" | "minor" confidence: float # 0-1 class KeyDetector: """ 基于 Krumhansl-Schmuckler 算法的调性检测 """ # 12 个大调的标准音高分布模板 # 数值来自 Krumhansl & Kessler (1982) 的心理学实验: # 受试者听一个调性上下文后,对 12 个音做出"适配度"评分 MAJOR_PROFILE = np.array([ 6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88 ]) # 小调模板(用相对大调的平移) MINOR_PROFILE = np.array([ 6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17 ]) NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] def detect(self, midi_pitch_counts: List[int]) -> KeyDetectionResult: """ 输入:12 维向量,每个位置是该音高的出现次数 输出:最可能的调性、调式和置信度 """ histogram = np.array(midi_pitch_counts, dtype=float) # 避免除零:加 1 平滑 histogram = histogram + 1 # 归一化:各音高比例而非绝对次数 histogram = histogram / histogram.sum() best_corr = -1 best_key_idx = 0 best_mode = "major" # 遍历 24 个调性(12 个大调 + 12 个小调) for key_idx in range(12): # 将模板旋转到当前调性 # 如 C 大调的模板是 [C, C#, D, ...],D 大调是 [D, D#, E, ...] major_rotated = np.roll(self.MAJOR_PROFILE, key_idx) minor_rotated = np.roll(self.MINOR_PROFILE, key_idx) # Pearson 相关系数衡量匹配度 # 使用相关性而非欧氏距离:绝对数值大小不重要, # 重要的是"哪些音该多"的分布形状 major_corr = np.corrcoef(histogram, major_rotated)[0, 1] minor_corr = np.corrcoef(histogram, minor_rotated)[0, 1] if major_corr > best_corr: best_corr = major_corr best_key_idx = key_idx best_mode = "major" if minor_corr > best_corr: best_corr = minor_corr best_key_idx = key_idx best_mode = "minor" return KeyDetectionResult( key=self.NOTE_NAMES[best_key_idx], mode=best_mode, confidence=float(best_corr), )

和弦功能分类与候选生成

from enum import Enum class ChordFunction(Enum): TONIC = "T" # 主功能:稳定、起点/终点 SUBDOMINANT = "S" # 下属功能:推进、发展 DOMINANT = "D" # 属功能:紧张、需要解决到主 # 大调中的和弦功能映射 # 为什么只有 T/S/D 三个分类: # 和声功能理论的精髓是将 7 个自然和弦归为三类 # 同类和弦可以互相替换(如 vi 可以替代 I 作主功能) MAJOR_FUNCTION_MAP = { "I": ChordFunction.TONIC, "iii": ChordFunction.TONIC, "vi": ChordFunction.TONIC, "IV": ChordFunction.SUBDOMINANT, "ii": ChordFunction.SUBDOMINANT, "V": ChordFunction.DOMINANT, "vii°": ChordFunction.DOMINANT, } # 功能进行规则:允许的功能转换 # T→S→D→T 是经典进行模式 ALLOWED_PROGRESSIONS = { # (当前功能, 下一个): 权重(越高越推荐) (ChordFunction.TONIC, ChordFunction.SUBDOMINANT): 0.9, (ChordFunction.TONIC, ChordFunction.DOMINANT): 0.8, (ChordFunction.TONIC, ChordFunction.TONIC): 0.4, # 不推荐连续主功能 (ChordFunction.SUBDOMINANT, ChordFunction.DOMINANT): 0.9, (ChordFunction.SUBDOMINANT, ChordFunction.TONIC): 0.7, (ChordFunction.SUBDOMINANT, ChordFunction.SUBDOMINANT): 0.3, # 不推荐连续下属 (ChordFunction.DOMINANT, ChordFunction.TONIC): 1.0, # 强烈推荐 D→T 解决 (ChordFunction.DOMINANT, ChordFunction.SUBDOMINANT): 0.5, (ChordFunction.DOMINANT, ChordFunction.DOMINANT): 0.1, # 不推荐连续属 } class ChordProgressionGenerator: """基于功能约束生成候选和弦进行""" def __init__(self, key_detector: KeyDetector, model_path: str): self.key_detector = key_detector # 加载微调过的 Transformer 模型 self.model = self._load_model(model_path) def generate( self, melody_midi: List[Tuple[int, float, float]], # (pitch, start_time, duration) style: str = "pop", # "pop" | "jazz" | "classical" num_variants: int = 3, ) -> List[dict]: """ 生成和弦推荐 返回 num_variants 个风格化变体,每个包含: - chords: [(chord_name, start_bar), ...] - style: 风格标签 - score: 推荐度评分 """ # Step 1: 统计旋律音高分布 pitch_counts = self._count_pitch_occurrences(melody_midi) # Step 2: 调性检测 key_result = self.key_detector.detect(pitch_counts) if key_result.confidence < 0.3: raise ValueError("无法确定旋律调性,音高信息不足") # Step 3: 按乐句分段,每段分配和弦 phrases = self._segment_phrases(melody_midi) bar_chords = self._assign_chords_to_phrases( phrases, key_result, style, num_variants ) return bar_chords def _count_pitch_occurrences(self, melody): """统计 12 个音高类别的出现频次""" counts = [0] * 12 for pitch, _, duration in melody: pitch_class = pitch % 12 # 用时长加权:长音符比短音符更能体现调性 counts[pitch_class] += duration return counts def _segment_phrases(self, melody, min_bars=4): """按休止符和长音切分乐句""" # 简化的乐句分段逻辑 pass def _assign_chords_to_phrases(self, phrases, key, style, variants): """使用 Transformer 模型为每段乐句匹配和弦""" pass def _load_model(self, path): pass

四、AI 和弦推荐的边界

缺点:

  1. 调性检测在转调段落失效:Krumhansl-Schmuckler 算法假设旋律调性不变。如果旋律中途转调,需要滑动窗口分段检测,但转调点的和弦推荐置信度会显著下降。
  2. 风格化依赖训练数据:爵士风格的和弦排列规律(如 ii-V-I 进行、三全音替代)远比流行复杂,模型训练数据中爵士的比例往往不够。
  3. "好听"的主观性:和弦推荐的目标函数(声部平滑度、功能正确性)与用户主观感受可能不一致。需要 AB 测试量表化反馈。

禁用场景:

  • 无调性/实验音乐:功能理论和声在此完全不适用。
  • 旋律太短(< 4 小节):统计信息不足,调性检测不可靠。

五、总结

AI 辅助和弦推荐的核心链路:调性检测→和弦功能约束→Transformer 风格化推荐。Krumhansl-Schmuckler 算法通过相关性计算确定调性,功能分类(T/S/D)约束生成空间,Transformer 在约束空间内做上下文感知的排列生成。生产落地时,调性检测的置信度阈值和风格化训练数据的质量和覆盖面,是决定推荐质量的两个关键变量。