🎵 第19课:音效引擎(FM合成)

FM频率调制 + 包络发生器 + 多音色 — 用FPGA产生丰富音色!

🏆 成就:FM音色师 ✅ Verilator验证通过

🎵
FM音色师
2算子FM合成 + ADSR包络 + 多种音色参数

🎵 FM合成原理

FM(频率调制)合成是1980年代雅马哈DX7合成器的核心算法——用一个正弦波(调制器)去调制另一个正弦波(载波)的频率,产生丰富的泛音!

FM合成信号流 ┌──────────┐ ┌──────────┐ │ 调制器 │ │ 载波 │ │ sin(f_m) │────→│ sin(f_c +│──→ ×包络 ──→ 输出 │ │ ×深度│ Δf·mod) │ └──────────┘ └──────────┘ 输出 = A(t) · sin(2π·f_c·t + I·sin(2π·f_m·t)) f_c = 载波频率(音高) f_m = 调制频率(决定泛音间距) I = 调制深度(决定泛音丰富度) f_m/f_c = 1:1 → 和声泛音(温暖) f_m/f_c = 2:1 → 八度泛音(明亮) f_m/f_c = 3:1 → 十二度泛音(金属) f_m/f_c = 非整数 → 不协和(钟声)

📐 数控振荡器(NCO)

Verilognco.v — 相位累加器+正弦LUT
// 数控振荡器(NCO) - 用相位累加器生成正弦波
module nco #(
    parameter PHASE_W = 16,   // 相位位宽
    parameter FREQ_W  = 20    // 频率控制字位宽
)(
    input  wire              clk,
    input  wire              rst,
    input  wire [FREQ_W-1:0] freq_word,  // 频率控制字
    output wire [7:0]        sine_out,   // 正弦波输出(0~255)
    output wire [7:0]        saw_out     // 锯齿波输出
);
    // 相位累加器
    reg [PHASE_W-1:0] phase;
    always @(posedge clk) begin
        if (rst) phase <= 0;
        else     phase <= phase + freq_word[PHASE_W-1:0];
    end

    // 锯齿波 = 相位直出
    assign saw_out = phase[PHASE_W-1:PHASE_W-8];

    // 正弦波查找表(256点, 8位)
    reg [7:0] sine_lut [0:255];
    initial begin
        // 预计算正弦表: sin(2π*i/256) * 127 + 128
        integer i;
        for(i=0; i<256; i=i+1) begin
            // 简化: 用分段线性近似
            if (i < 64)
                sine_lut[i] = 128 + (i * 2);
            else if (i < 128)
                sine_lut[i] = 255 - ((i - 64) * 2);
            else if (i < 192)
                sine_lut[i] = 128 - ((i - 128) * 2);
            else
                sine_lut[i] = (i - 192) * 2;
        end
    end

    assign sine_out = sine_lut[phase[PHASE_W-1:PHASE_W-8]];
endmodule

📈 ADSR包络发生器

Verilogadsr_envelope.v — 攻击衰减持续释放
// ADSR包络发生器
// Attack-Decay-Sustain-Release 四段包络
module adsr_envelope (
    input  wire        clk,
    input  wire        rst,
    input  wire        gate,      // 1=音符开启, 0=释放
    input  wire [3:0]  attack,    // 攻击速率
    input  wire [3:0]  decay,     // 衰减速率
    input  wire [7:0]  sustain,   // 持续电平
    input  wire [3:0]  release,   // 释放速率
    output reg  [7:0]  level      // 当前包络电平
);
    localparam IDLE=0, ATK=1, DCY=2, SUS=3, REL=4;
    reg [2:0] state;
    reg [15:0] timer;

    always @(posedge clk) begin
        if (rst) begin
            state <= IDLE; level <= 0; timer <= 0;
        end else begin
            case(state)
                IDLE: begin
                    level <= 0;
                    if (gate) state <= ATK;
                end
                ATK: begin
                    if (!gate) state <= REL;
                    else if (level >= 255) state <= DCY;
                    else level <= level + (attack + 1);
                end
                DCY: begin
                    if (!gate) state <= REL;
                    else if (level <= sustain) state <= SUS;
                    else level <= level - (decay + 1);
                end
                SUS: begin
                    if (!gate) state <= REL;
                    else level <= sustain;
                end
                REL: begin
                    if (gate) state <= ATK;
                    else if (level == 0) state <= IDLE;
                    else level <= level - (release + 1);
                end
            endcase
        end
    end
endmodule

🎶 2算子FM合成器

Verilogfm_synth.v — 完整FM合成
// FM合成器 - 2算子FM
// 输出 = carrier * envelope
// carrier = sin(carrier_phase + mod_index * modulator)
module fm_synth (
    input  wire        clk,
    input  wire        rst,
    input  wire        gate,
    input  wire [19:0] carrier_freq, // 载波频率字
    input  wire [19:0] mod_freq,     // 调制频率字
    input  wire [7:0]  mod_index,    // 调制深度
    input  wire [3:0]  attack, decay, release,
    input  wire [7:0]  sustain,
    output wire [7:0]  audio_out
);
    // 调制器NCO
    wire [7:0] mod_sine;
    nco #(.PHASE_W(16), .FREQ_W(20))
        mod_osc (.clk(clk), .rst(rst),
                 .freq_word(mod_freq), .sine_out(mod_sine), .saw_out());

    // 载波NCO - 频率被调制
    wire [19:0] modulated_freq = carrier_freq +
                                 {mod_sine[7:0], mod_index[7:0], 4'b0};
    wire [7:0] carrier_sine;
    nco #(.PHASE_W(16), .FREQ_W(20))
        car_osc (.clk(clk), .rst(rst),
                 .freq_word(modulated_freq), .sine_out(carrier_sine), .saw_out());

    // ADSR包络
    wire [7:0] env_level;
    adsr_envelope u_env (
        .clk(clk), .rst(rst), .gate(gate),
        .attack(attack), .decay(decay),
        .sustain(sustain), .release(release),
        .level(env_level)
    );

    // 输出 = 载波 × 包络
    assign audio_out = (carrier_sine[7:1] * env_level[7:1]);
endmodule

🧪 FM合成器测试

SystemVerilogfm_synth_tb.sv — FM合成测试台
module fm_synth_tb;
    logic clk=0, rst=1, gate=0;
    logic [19:0] carrier_freq, mod_freq;
    logic [7:0] mod_index;
    logic [3:0] attack=4'd2, decay=4'd2, release=4'd4;
    logic [7:0] sustain=8'd128;
    logic [7:0] audio_out;

    fm_synth uut(.*);
    always #10 clk = ~clk;

    initial begin
        rst=1; #50; rst=0;
        // C4 = 262Hz, freq_word = 262 * 2^20 / 50000000 ≈ 5500
        carrier_freq = 20'd5500;
        mod_freq = 20'd5500; // 1:1频率比
        mod_index = 8'd50;

        $display("--- FM合成测试 ---");
        // 按键
        gate = 1; #5000;
        $display("  Attack阶段: audio=%0d", audio_out);
        #10000;
        $display("  Sustain阶段: audio=%0d", audio_out);
        // 松键
        gate = 0; #10000;
        $display("  Release阶段: audio=%0d", audio_out);
        $display("FM合成测试完成 ✓");
        #1000; $finish;
    end
endmodule

📊 音色参数表

音色载波:调制比调制深度AttackDecaySustain
电钢琴1:130
贝斯1:180
钟声1:3.5120
风琴1:250
铜管1:160+包络
金属1:1.41200

💡 频率计算:MIDI音符号→频率:f = 440 × 2^((n-69)/12)。NCO频率字 = f × 2^PHASE_W / fclk。例:C4(262Hz)在50MHz/16bit相位时 = 262×65536/50000000 ≈ 343。

🏋️ 练习

练习1:用FM合成播放C大调音阶(C4-C5)

练习2:实现4算子FM合成(2个调制器+1个载波+1个反馈)

练习3:添加包络控制调制深度,模拟铜管音色

练习4:实现多复音:3个FM合成器同时发声

练习5:用PS/2键盘弹奏不同音符

🎵
FM音色师
完成本课练习,掌握FM合成原理!

🔧 Verilator验证步骤

步骤1verilator --lint-only nco.v — 验证NCO振荡器

步骤2verilator --lint-only adsr_envelope.v — 验证包络发生器

步骤3verilator --lint-only fm_synth.v — 验证FM合成器

步骤4verilator --binary -j 0 nco.v adsr_envelope.v fm_synth.v fm_synth_tb.sv

步骤5./obj_dir/Vfm_synth_tb — 运行仿真

🔬 FM合成器扩展:4算子FM

DX7使用6算子FM合成,我们可以实现4算子版本来获得更丰富的音色:

4算子FM算法(8种配置之一) ┌──────┐ ┌──────┐ │ Op1 │────→│ Op2 │────→ 输出 │(调制)│ │(载波)│ └──────┘ └──────┘ ↑ ↑ ┌──────┐ ┌──────┐ │ Op3 │────→│ Op4 │ │(调制)│ │(调制)│ └──────┘ └──────┘ 4个NCO + 4个包络 = 丰富音色 LUT消耗: ~200, BRAM: 4×256字节

💡 MIDI音符频率计算详解

公式:f = 440 × 2^((n-69)/12),n为MIDI音符号

NCO频率字:freq_word = f × 2^PHASE_W / fclk

:A4(MIDI#69) = 440Hz, 50MHz/16bit相位

freq_word = 440 × 65536 / 50000000 = 576

半音比:2^(1/12) ≈ 1.0595,每高半音频率乘1.0595

八度:每高一个八度,频率字翻倍