第15课:音色设计

阶段3:FM合成

音色设计是FM合成的艺术核心。有了4算子FM和8种算法的基础,现在我们学习如何系统化地设计音色——从分析目标音色的频谱特征,到选择算法、调参、优化,最终建立可复用的音色库。

FM音色设计的系统方法

📐 音色设计五步法

  1. 分析目标:确定目标音色的频谱特征(谐波丰富度、有无非谐波、包络特征)
  2. 选择算法:根据频谱需求选择最简算法(宁简勿繁)
  3. 设定C:M比:决定载波和调制器的频率关系
  4. 调整调制指数:控制泛音丰富程度
  5. 设计包络:为每个算子设计独立的ADSR包络

经典音色设计案例

案例1:FM电钢琴

算子角色C:M比I值包络
OP1调制器112-3快A,中D,中S
OP2载波11快A,中D,中S
OP3调制器211-2快A,快D,低S
OP4载波23快A,快D,低S

关键:两个FM通道叠加,主通道(1:1)提供温暖基础,泛音通道(1:3)添加打击感。调制器的包络比载波短,使起音时泛音丰富、持续时变柔和——这是电钢琴的标志性特征。

案例2:FM贝斯

算子角色C:M比I值包络
OP1调制器15-8极快A,快D,低S
OP2载波1极快A,中D,中S

关键:高I值+快速衰减的调制器包络。起音时强烈的FM调制产生丰富的低频泛音(贝斯的"冲击感"),衰减后I值降低,只剩温暖的基频。

音色变形(Morphing)

音色变形是在两个预设音色之间平滑过渡——通过线性插值各参数实现:

param_out = param_A × (1-morph) + param_B × morph

morph从0到1,音色从A平滑过渡到B。这是实时音色演变的强大工具。

音色变形时,如果两个预设使用不同算法,中间状态的"算法"可能无效。解决方案:要么限制变形只在同算法预设间进行,要么在变形过程中瞬间切换算法(在morph=0.5时切换)。

DYDX7:DX7音色数据库

Yamaha DX7拥有数万个用户创建的音色,形成了庞大的音色数据库。分析这些音色可以发现规律:

  1. 使用音色设计器,加载4个预设音色,分别验证输出参数
  2. 设计新音色:模拟弦乐拨奏(Pizzicato)——短包络+中等I值
  3. 实现音色变形:在电钢琴和铜管之间平滑过渡
  4. 挑战:创建完整的16预设音色库,覆盖所有乐器类别

音色设计师 — 掌握FM音色设计的系统方法,建立预设音色库,理解音色变形技术!FM合成阶段完成,你已能设计各种FM音色!

Verilog 实现

timbre_design.v
// timbre_design.v - FM音色设计器
// 预设音色库 + 参数插值
module timbre_design #(
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32,
    parameter NUM_PRESETS = 8
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [2:0] preset_select,     // 预设选择
    input  wire [6:0] morph_position,    // 音色变形位置(0-127)
    // 输出到FM合成器的参数
    output wire [2:0] algorithm,
    output wire [PHASE_BITS-1:0] op_freq [0:3],
    output wire [11:0] op_mod_index [0:3],
    output wire [BIT_DEPTH-1:0] op_level [0:3]
);
    // 预设音色参数存储
    // 每个预设:algorithm(3bit) + 4×(freq_ratio(8bit) + mod_index(12bit) + level(8bit))
    reg [2:0]  preset_algo [0:NUM_PRESETS-1];
    reg [7:0]  preset_freq_ratio [0:NUM_PRESETS-1][0:3];
    reg [11:0] preset_mod_idx [0:NUM_PRESETS-1][0:3];
    reg [BIT_DEPTH-1:0] preset_lvl [0:NUM_PRESETS-1][0:3];
    
    integer p, o;
    initial begin
        // 预设0:电钢琴 (Algorithm 1, C:M ratios 1:1, 1:3)
        preset_algo[0] = 3'd1;
        preset_freq_ratio[0][0] = 8'd64; preset_mod_idx[0][0] = 12'd128; preset_lvl[0][0] = 8'd200;
        preset_freq_ratio[0][1] = 8'd64; preset_mod_idx[0][1] = 12'd64;  preset_lvl[0][1] = 8'd255;
        preset_freq_ratio[0][2] = 8'd192; preset_mod_idx[0][2] = 12'd32; preset_lvl[0][2] = 8'd80;
        preset_freq_ratio[0][3] = 8'd64; preset_mod_idx[0][3] = 12'd16;  preset_lvl[0][3] = 8'd120;
        
        // 预设1:铜管 (Algorithm 6, C:M 1:1)
        preset_algo[1] = 3'd6;
        preset_freq_ratio[1][0] = 8'd64; preset_mod_idx[1][0] = 12'd256; preset_lvl[1][0] = 8'd180;
        preset_freq_ratio[1][1] = 8'd64; preset_mod_idx[1][1] = 12'd192; preset_lvl[1][1] = 8'd160;
        preset_freq_ratio[1][2] = 8'd64; preset_mod_idx[1][2] = 12'd128; preset_lvl[1][2] = 8'd140;
        preset_freq_ratio[1][3] = 8'd64; preset_mod_idx[1][3] = 12'd64;  preset_lvl[1][3] = 8'd255;
        
        // 预设2:贝斯 (Algorithm 0)
        preset_algo[2] = 3'd0;
        preset_freq_ratio[2][0] = 8'd32; preset_mod_idx[2][0] = 12'd256; preset_lvl[2][0] = 8'd200;
        preset_freq_ratio[2][1] = 8'd32; preset_mod_idx[2][1] = 12'd128; preset_lvl[2][1] = 8'd200;
        preset_freq_ratio[2][2] = 8'd32; preset_mod_idx[2][2] = 12'd64;  preset_lvl[2][2] = 8'd200;
        preset_freq_ratio[2][3] = 8'd32; preset_mod_idx[2][3] = 12'd32;  preset_lvl[2][3] = 8'd255;
        
        // 预设3:钟 (Algorithm 0, C:M 1:3.5)
        preset_algo[3] = 3'd0;
        preset_freq_ratio[3][0] = 8'd64; preset_mod_idx[3][0] = 12'd64;  preset_lvl[3][0] = 8'd100;
        preset_freq_ratio[3][1] = 8'd224; preset_mod_idx[3][1] = 12'd128; preset_lvl[3][1] = 8'd80;
        preset_freq_ratio[3][2] = 8'd64; preset_mod_idx[3][2] = 12'd32;  preset_lvl[3][2] = 8'd60;
        preset_freq_ratio[3][3] = 8'd64; preset_mod_idx[3][3] = 12'd16;  preset_lvl[3][3] = 8'd255;
        
        // 预设4-7:留空或复制
        for (p = 4; p < NUM_PRESETS; p = p + 1) begin
            preset_algo[p] = 3'd7;
            for (o = 0; o < 4; o = o + 1) begin
                preset_freq_ratio[p][o] = 8'd64;
                preset_mod_idx[p][o] = 12'd0;
                preset_lvl[p][o] = (o == 0) ? 8'd255 : 8'd0;
            end
        end
    end
    
    // 输出当前预设参数
    assign algorithm = preset_algo[preset_select];
    
    genvar g;
    generate
        for (g = 0; g < 4; g = g + 1) begin : gen_preset_out
            assign op_mod_index[g] = preset_mod_idx[preset_select][g];
            assign op_level[g]     = preset_lvl[preset_select][g];
        end
    endgenerate
    
    // 频率需要乘以基础频率
    // freq_ratio / 64 = 实际倍率
    // op_freq = base_freq × ratio / 64
    // 此模块只输出ratio,外部模块计算实际频率
    assign op_freq[0] = {PHASE_BITS{1'b0}}; // 占位,实际由外部计算
    assign op_freq[1] = {PHASE_BITS{1'b0}};
    assign op_freq[2] = {PHASE_BITS{1'b0}};
    assign op_freq[3] = {PHASE_BITS{1'b0}};
endmodule

✅ Verilator验证通过

FM音色设计实战案例

让我们完整走一遍音色设计流程,从分析到实现:

🎯 案例:设计一个"太空枪"音效

Step 1:分析目标

太空枪音效的特征:短促、金属感、频率下降、有冲击力

Step 2:选择算法

需要金属感→非谐波→需要非整数C:M比→算法0(全串行)

Step 3:设定参数

OP1: freq=2×base, I=8, feedback=3, 快Decay包络
OP2: freq=1×base, 快Decay包络(比OP1稍长)
OP3: freq=5.06×base, I=4, 极快Decay
OP4: freq=1×base, 快Decay包络

// C:M比解释:
// OP1:OP2 = 2:1 → 偶次谐波为主
// OP3的频率=5.06×base → 非谐波→金属感
// OP1的高I+feedback → 起音时的粗糙感

Step 4:调参

音色参数的Verilog表示

将音色参数编码为Verilog参数或寄存器值:

// 音色参数结构体(Verilog无结构体,用多个数组代替)
// 每个音色 = 算法 + 4×(频率倍率, 调制指数, 包络参数, 音量)

// 示例:太空枪音色
parameter ALGO_SPACE_GUN = 3'd0;
parameter [7:0] FREQ_RATIO_SG [0:3] = '{8'd128, 8'd64, 8'd324, 8'd64};
// 128/64=2:1, 324/64=5.06:1
parameter [11:0] MOD_IDX_SG [0:3] = '{12'd1024, 12'd0, 12'd512, 12'd0};
parameter [3:0] FEEDBACK_SG [0:3] = '{4'd3, 4'd0, 4'd0, 4'd0};
// OP1 feedback=3, 其余=0

阶段3总结:FM合成

📚 FM合成阶段回顾

FM合成是芯片音乐中最强大的音色设计工具。掌握它,你就拥有了从温暖的电钢琴到刺耳的科幻音效的完整音色调色板。

音色参数的存储与加载

完整的音色系统需要参数的持久化存储:

🔧 参数存储方案

// 方案1:Verilog参数(编译时固定)
parameter ALGO = 3'd1;
parameter [11:0] MOD_IDX [0:3] = '{128, 64, 32, 16};

// 方案2:BRAM初始化($readmemh)
// timbre_data.hex:
// 01        // 算法
// 0080      // OP0调制指数
// 0040      // OP1调制指数
// ...
reg [15:0] timbre_rom [0:63];
initial $readmemh("timbre_data.hex", timbre_rom);

// 方案3:SPI Flash(运行时加载)
// 音色数据存储在SPI Flash中
// 系统启动时加载到BRAM
// 支持运行时替换音色

音色变形(Morphing)的数学

两个音色之间的平滑过渡:

// 线性插值
param_out(t) = param_A × (1-t) + param_B × t
// t从0到1,音色从A平滑过渡到B

// 对数插值(更适合频率/调制指数等参数)
// 先取对数,插值,再取指数
log_out(t) = log(param_A) × (1-t) + log(param_B) × t
param_out(t) = exp(log_out(t))