第14课:算法选择

阶段3:FM合成

选择正确的FM算法是音色设计的关键决策。不同的算法拓扑产生截然不同的频谱特征,即使其他参数完全相同。本课深入分析8种算法的频谱特性和适用场景,建立算法选择的系统方法论。

算法选择的系统方法

📐 算法选择决策树

需要FM音色吗?
├─ 否 → 算法7(全并行/加法合成)
└─ 是 → 需要多丰富的泛音?
    ├─ 极丰富(金属/钟声)→ 算法0(全串行)
    ├─ 丰富(铜管/合成器)→ 算法6(三调制)
    ├─ 中等(电钢琴/贝斯)→ 算法1(双串行)
    ├─ 较少(温暖领奏)→ 算法3/5(混合)
    └─ 最少(柔和谐波)→ 算法2(单FM+正弦)

逐算法深入分析

算法0:全串行 — "超级调制器"

3级调制链使频谱极度复杂,产生大量非谐波成分。适合:

缺点:参数敏感,微小变化导致音色剧变,难以精确控制。

算法1:双串行 — "工作马"

最实用的算法之一。两个独立FM通道叠加,一个负责主音色,另一个负责泛音或谐波。适合:

算法6:三调制 — "铜管专家"

三个调制器同时调制一个载波,产生非常密集的频谱。Yamaha推荐用于:

算法7:全并行 — "加法合成"

没有调制,4个独立正弦波叠加。等价于4声部加法合成器。适合:

🔊 算法选择的历史洞察

Yamaha DX7的32个预设音色中,算法使用分布:

结论:中等复杂度的算法最实用,极端算法用于特殊场景。

调制深度随时间变化

FM音色最重要的表现力来源是调制深度随包络变化

这种"音色包络"是FM合成模拟真实乐器的核心技巧——真实乐器的泛音确实随时间衰减。

  1. 实现算法选择器,验证路由矩阵和输出掩码
  2. 为每种算法设计一个典型音色参数集
  3. 对比:同一参数在不同算法下的频谱差异
  4. 挑战:实现动态算法切换——在音符播放过程中切换算法,制造音色变化

算法大师 — 深入理解8种FM算法的频谱特性,建立系统化的算法选择方法论,掌握音色包络概念!

Verilog 实现

fm_algorithm_sel.v
// fm_algorithm_sel.v - FM算法选择器与参数化路由
// 动态配置算子之间的调制关系
module fm_algorithm_sel #(
    parameter NUM_OPS = 4,
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [2:0] algorithm,
    // 调制路由矩阵:哪些算子的输出连接到哪些算子的输入
    output wire [NUM_OPS-1:0] [NUM_OPS-1:0] route_matrix,
    // 输出掩码:哪些算子输出到最终混音
    output wire [NUM_OPS-1:0] output_mask
);
    // 路由矩阵:route_matrix[target][source] = 1表示source调制target
    reg [NUM_OPS*NUM_OPS-1:0] route_reg;
    reg [NUM_OPS-1:0] mask_reg;
    
    always @(*) begin
        route_reg = {(NUM_OPS*NUM_OPS){1'b0}};
        mask_reg  = {NUM_OPS{1'b0}};
        
        case (algorithm)
            3'd0: begin // 全串行:0→1→2→3
                route_reg[4*1+0] = 1'b1;  // OP0调制OP1
                route_reg[4*2+1] = 1'b1;  // OP1调制OP2
                route_reg[4*3+2] = 1'b1;  // OP2调制OP3
                mask_reg[3] = 1'b1;        // 只有OP3输出
            end
            3'd1: begin // 双串行:0→1, 2→3
                route_reg[4*1+0] = 1'b1;
                route_reg[4*3+2] = 1'b1;
                mask_reg[1] = 1'b1;
                mask_reg[3] = 1'b1;
            end
            3'd2: begin // 0→1, 2和3独立输出
                route_reg[4*1+0] = 1'b1;
                mask_reg[1] = 1'b1;
                mask_reg[2] = 1'b1;
                mask_reg[3] = 1'b1;
            end
            3'd3: begin // 0+1→2, 3独立
                route_reg[4*2+0] = 1'b1;
                route_reg[4*2+1] = 1'b1;
                mask_reg[2] = 1'b1;
                mask_reg[3] = 1'b1;
            end
            3'd4: begin // 0→1, 2→3 (并联FM)
                route_reg[4*1+0] = 1'b1;
                route_reg[4*3+2] = 1'b1;
                mask_reg[1] = 1'b1;
                mask_reg[3] = 1'b1;
            end
            3'd5: begin // 0+1→2, 3独立(变体)
                route_reg[4*2+0] = 1'b1;
                route_reg[4*2+1] = 1'b1;
                mask_reg[2] = 1'b1;
                mask_reg[3] = 1'b1;
            end
            3'd6: begin // 三调制:0+1+2→3
                route_reg[4*3+0] = 1'b1;
                route_reg[4*3+1] = 1'b1;
                route_reg[4*3+2] = 1'b1;
                mask_reg[3] = 1'b1;
            end
            3'd7: begin // 全并行(加法合成)
                mask_reg[0] = 1'b1;
                mask_reg[1] = 1'b1;
                mask_reg[2] = 1'b1;
                mask_reg[3] = 1'b1;
            end
        endcase
    end
    
    assign route_matrix = route_reg;
    assign output_mask = mask_reg;
endmodule

✅ Verilator验证通过

算法选择的高级技巧

除了基本的算法选择决策树,还有一些高级技巧:

🔧 算法组合技巧

  1. 双层叠加:两个通道使用相同音符但不同算法,叠加后音色更厚
  2. 高低频分离:一个通道处理基频(算法7并行),另一个处理泛音(算法0串行)
  3. 动态切换:在音符播放过程中切换算法——起音用算法0(全串行),持续用算法1(双串行)
  4. 算子层叠:同一通道在不同八度使用不同算子,制造更厚的声场

实时算法切换的实现

在硬件中实现运行时算法切换需要谨慎:

// 算法切换可能导致的问题:
// 1. 输出突然跳变 → 咔嗒声
// 2. 调制器状态不一致 → 短暂的噪声
// 3. 包络重新触发 → 音色不连贯

// 安全的切换策略:
// 1. 在音符间隙切换(最安全)
// 2. 淡出→切换→淡入(平滑)
// 3. 在过零点切换(需要过零检测)

从算法到音色的映射数据库

建立一个系统化的算法-音色映射,有助于快速设计:

目标音色推荐算法关键参数参考预设
电钢琴1I=1-3, C:M=1:1+1:3Dexed "Hard Rhodes"
Clavinet2I=3-5, 快DecayDexed "Clav"
弦乐Pad1I=1-2, 慢AttackDexed "Strings"
铜管6I=5-7, C:M=1:1Dexed "Brass"
贝斯0I=5-8, C:M=1:1Dexed "Bass"
钟/铃0I=3, C:M=1:3.5Dexed "Bells"
笛/Flute7I≈0, 纯正弦Dexed "Flute"
噪声/鼓0I=10+, 高反馈自定义

算法切换的平滑过渡

在音符播放过程中切换算法需要处理信号连续性:

🔧 交叉淡入淡出方案

// 在两个算法之间交叉淡入淡出
// 1. 同时运行两个算法
// 2. 用淡入淡出因子混合输出
// 3. 淡入淡出持续时间约10ms

reg [7:0] xfade_counter;
wire [7:0] algo_a_out = compute_algorithm(algo_old);
wire [7:0] algo_b_out = compute_algorithm(algo_new);
wire [7:0] mix = (algo_a_out * (255 - xfade_counter) + 
                  algo_b_out * xfade_counter) >> 8;

always @(posedge clk) begin
    if (switch_trigger) xfade_counter <= 8'd0;
    else if (xfade_counter < 255) xfade_counter <= xfade_counter + 1;
end

算法与音乐风格的对应

不同音乐风格偏好不同的算法:

风格首选算法原因
流行1,2需要清晰可辨的旋律音色
电子/Techno0,6需要丰富、激进的音色
氛围/Ambient7,5需要柔和、融合的音色
游戏音效0,3需要独特、辨识度高的音色
管风琴7纯加法合成,drawbar风格

算法选择实验

通过实验理解不同算法的音色差异:

🔬 实验方案

// 控制变量法实验
// 固定参数:所有算子同频率(C:M=1:1)
//           相同调制指数(I=3)
//           相同包络
// 变量:算法选择(0-7)

// 预期结果:
// 算法0:最丰富(3级调制),接近噪声
// 算法1:两个独立FM,较丰富
// 算法2:1个FM+2个正弦,中等
// 算法3:双调制+独立载波,中等
// 算法7:4个纯正弦,最简单

// 实验方法:
// 1. 用同一组基础参数运行所有8种算法
// 2. 录制每种算法的输出
// 3. 对比波形和听感
// 4. 找到最适合目标音色的算法

📐 算法切换的音色变化记录

以下是同一组参数(C:M=1:1, I=3, 4个算子同频)在不同算法下的音色特征记录:

算法0(全串行): 非常丰富,接近噪声,适合特殊效果
算法1(双串行): 中等丰富,温暖+金属,最实用
算法2(串行+并行): 主体FM+辅助正弦,有层次
算法3(双调制): 中等,两个调制器增加复杂度
算法7(全并行): 4个纯音叠加,类似管风琴

结论:对于大多数应用,算法1是最佳起点