第12课:2算子FM

阶段3:FM合成

2算子FM是FM合成的最基本形式,也是理解更复杂FM架构的基础。Yamaha OPL系列(用于Sound Blaster声卡)的核心就是2算子FM。本课实现完整的2算子FM合成器,包含调制器反馈和算法选择。

算子(Operator)的概念

FM合成中的"算子"是一个正弦波振荡器 + 包络发生器的组合单元。每个算子可以:

📐 2算子FM的两种算法

算法0(串行FM):
  Mod → Carrier → 输出
  调制器调制载波频率,产生丰富泛音

算法1(并行叠加):
  Mod → 输出
  Carrier → 输出  
  两个算子独立输出,叠加为更厚的音色

串行FM是"真正的"FM合成,产生非谐波泛音。并行叠加只是两个正弦波的叠加,等价于加法合成。

Yamaha OPL芯片系列

芯片算子数通道数算法应用
YM3526 (OPL)29串行/并行早期声卡
YM3812 (OPL2)29串行/并行Adlib/SB
YMF262 (OPL3)4184种SB16
YM2151 (OPM)488种X68000
YM2612 (OPN2)468种Mega Drive

调制器反馈

调制器的输出反馈到自身输入,形成自调制(Self-modulation)。这是2算子FM的重要增强:

OPL2的反馈参数是3位(0-7),0=无反馈,7=最大反馈。在Doom的配乐中,你可以听到高反馈设置产生的"粗糙"音色——那些低沉的吉他模拟声就是用2算子FM+高反馈实现的。

经典2算子FM音色配方

音色C:M比I值反馈说明
电钢琴1:11-20温暖柔和
贝斯1:13-50-1厚重低音
铜管1:15-70明亮有力
1:3.530非谐波,金属感
笛子1:10.50接近纯音
噪声鼓1:110+7高反馈+高I
  1. 实现并验证2算子FM,测试串行和并行两种算法
  2. 实验:固定C:M=1:1,从I=0逐步增大到I=10,观察波形变化
  3. 添加反馈:从0逐步增大反馈,观察调制器波形变化
  4. 挑战:为2算子FM添加独立的ADSR包络控制每个算子

FM实践者 — 实现2算子FM合成器,掌握串行/并行算法,理解调制器反馈的效果,能设计基本FM音色!

Verilog 实现

fm_2op.v
// fm_2op.v - 2算子FM合成器
// 经典2算子FM:1个调制器 + 1个载波
module fm_2op #(
    parameter CLK_FREQ  = 50000000,
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32,
    parameter SIN_ADDR_BITS = 10
)(
    input  wire clk,
    input  wire rst_n,
    // 载波参数
    input  wire [PHASE_BITS-1:0] carrier_freq,
    input  wire [BIT_DEPTH-1:0]  carrier_level,
    // 调制器参数
    input  wire [PHASE_BITS-1:0] mod_freq,
    input  wire [11:0]           mod_index,      // 调制指数×128
    // 反馈参数
    input  wire [3:0]            feedback_level,  // 调制器反馈量
    // 算法选择
    input  wire algorithm_sel,                    // 0=串行, 1=并行
    // 输出
    output wire [BIT_DEPTH-1:0] audio_out
);
    // 正弦查找表 (1024点)
    reg [BIT_DEPTH-1:0] sin_table [0:1023];
    integer i;
    real angle;
    initial begin
        for (i = 0; i < 1024; i = i + 1) begin
            angle = 3.14159265 * 2.0 * i / 1024.0;
            sin_table[i] = $rtoi(127.5 + 127.5 * $sin(angle));
        end
    end
    
    // ─── 调制器(Operator 1) ───
    reg [PHASE_BITS-1:0] mod_phase;
    reg signed [BIT_DEPTH:0] mod_feedback;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            mod_phase <= {PHASE_BITS{1'b0}};
            mod_feedback <= {BIT_DEPTH+1{1'b0}};
        end else begin
            mod_phase <= mod_phase + mod_freq;
            // 反馈:将前一次输出加到相位上
            mod_feedback <= $signed({1'b0, sin_table[mod_phase[PHASE_BITS-1 -: SIN_ADDR_BITS]]}) - 128;
        end
    end
    
    // 调制器输出(含反馈)
    wire [SIN_ADDR_BITS-1:0] mod_addr = mod_phase[PHASE_BITS-1 -: SIN_ADDR_BITS];
    wire [BIT_DEPTH-1:0] mod_raw = sin_table[mod_addr];
    wire signed [BIT_DEPTH:0] mod_value_signed;
    assign mod_value_signed = $signed({1'b0, mod_raw}) - 128;
    
    // 应用调制指数和反馈
    wire signed [BIT_DEPTH+11:0] mod_with_index;
    assign mod_with_index = mod_value_signed * $signed({1'b0, mod_index}) +
                            (mod_feedback * $signed({1'b0, feedback_level, 4'd0}));
    
    // ─── 载波(Operator 2) ───
    reg [PHASE_BITS-1:0] carrier_phase;
    
    // 相位调制:载波相位 + 调制器输出
    wire signed [PHASE_BITS:0] carrier_phase_mod;
    assign carrier_phase_mod = $signed({1'b0, carrier_phase}) + 
                               (mod_with_index >>> 12);
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            carrier_phase <= {PHASE_BITS{1'b0}};
        else
            carrier_phase <= carrier_phase + carrier_freq;
    end
    
    // 读取载波正弦值
    wire [SIN_ADDR_BITS-1:0] carrier_addr = carrier_phase_mod[PHASE_BITS-1 -: SIN_ADDR_BITS];
    wire [BIT_DEPTH-1:0] carrier_value = sin_table[carrier_addr];
    
    // 应用载波音量
    wire [15:0] out_product = carrier_value * carrier_level;
    assign audio_out = out_product[15:8];
endmodule

✅ Verilator验证通过

OPL2声卡的传奇地位

Yamaha YM3812(OPL2)是PC游戏音乐的里程碑:

🎮 OPL2游戏配乐经典

游戏年份音乐特色
Doom1993Bobby Prince的FM金属音色
德军总部3D1992紧张的FM氛围音乐
猴岛小英雄1990Michael Land的幽默FM配乐
SimCity 20001993轻松的Jazz FM风格
Duke Nukem 3D1996标志性的FM吉他和贝斯

OPL2的9个2算子通道在熟练的作曲家手中能产生令人惊叹的音乐。关键在于每个通道的参数配置——同样的硬件,不同的参数产生完全不同的音色。

2算子FM的参数空间探索

2算子FM只有4个关键参数,但组合起来音色无穷:

📐 参数空间与音色区域

        I(调制指数) →
     ┌─────────────────────┐
  低 │ 纯正弦 │ 温暖 │ 明亮 │
C:M │   区域 │ 区域 │ 区域 │
比  ├─────────────────────┤
  高 │ 钟声  │ 金属 │ 噪声 │
     │  区域 │ 区域 │ 区域 │
     └─────────────────────┘
         低 ← I(调制指数) → 高

C:M=1:1, I≈0: 纯正弦波(最简单)
C:M=1:1, I≈2: 温暖电钢琴
C:M=1:1, I≈5: 明亮铜管
C:M=1:3.5, I≈3: 金属钟声
C:M=1:√2, I≈5: 噪声化(混沌)

反馈对频谱的影响

反馈参数是2算子FM的"秘密武器":

反馈=0: 调制器输出纯正弦波 → 标准FM边带
反馈=1: 调制器轻微失真 → 增加少量偶次谐波
反馈=3: 调制器接近锯齿波 → 丰富泛音
反馈=5: 调制器严重失真 → 粗糙音色
反馈=7: 调制器进入混沌 → 接近噪声

反馈让2算子FM拥有了接近4算子的音色复杂度——这是OPL2虽然只有2算子但音色丰富的关键。

OPL2的寄存器编程

OPL2的音色通过寄存器写入配置,每个通道9组寄存器:

🔧 OPL2寄存器映射

$20-35: 波形选择 + 包络参数(每个算子)
$40-55: 音量/衰减(KSL + 总音量)
$60-75: Attack/Decay速率
$80-95: Sustain/Release速率
$A0-B8: 频率(10位) + 八度(3位)
$BD:    全局控制(深度, Tremolo/Vibrato使能)
$C0-C8: 通道配置(算法选择 + 反馈量)

每个通道只需要写入约10个寄存器就能定义一个完整音色。9个通道总共不到100字节的数据量——极其紧凑。

2算子FM的极限

2算子FM虽然简单,但有无法实现的音色:

2算子FM的局限性分析

理解2算子FM的限制有助于我们正确使用它:

⚠️ 2算子FM的五大限制

  1. 只有1级调制:无法产生4算子那样的深层调制链
  2. 单一调制源:调制器只有1个,无法模拟复杂泛音结构
  3. 并行模式=加法合成:不是真正的FM,只是两个正弦波叠加
  4. 音色随音高变化:同一参数在不同音高下听感不同(因为I值代表的频率偏移是绝对的)
  5. 无滤波器:无法在FM之后进一步塑造频谱

尽管有这些限制,OPL2仍然创造了无数经典游戏音乐。关键在于作曲家如何巧妙利用2算子的简洁性。

📐 OPL2经典音色参数

以下是OPL2游戏中常用的音色参数,可以直接用于我们的2算子FM模块:

音色名算法C:MI值反馈
Doom吉他串行1:163
Duke贝斯串行1:181
猴岛长笛串行1:110
SimCity钢琴并行1:120
Wolf弦乐串行1:111