音色设计是FM合成的艺术核心。有了4算子FM和8种算法的基础,现在我们学习如何系统化地设计音色——从分析目标音色的频谱特征,到选择算法、调参、优化,最终建立可复用的音色库。
| 算子 | 角色 | C:M比 | I值 | 包络 |
|---|---|---|---|---|
| OP1 | 调制器1 | 1 | 2-3 | 快A,中D,中S |
| OP2 | 载波1 | 1 | — | 快A,中D,中S |
| OP3 | 调制器2 | 1 | 1-2 | 快A,快D,低S |
| OP4 | 载波2 | 3 | — | 快A,快D,低S |
关键:两个FM通道叠加,主通道(1:1)提供温暖基础,泛音通道(1:3)添加打击感。调制器的包络比载波短,使起音时泛音丰富、持续时变柔和——这是电钢琴的标志性特征。
| 算子 | 角色 | C:M比 | I值 | 包络 |
|---|---|---|---|---|
| OP1 | 调制器 | 1 | 5-8 | 极快A,快D,低S |
| OP2 | 载波 | 1 | — | 极快A,中D,中S |
关键:高I值+快速衰减的调制器包络。起音时强烈的FM调制产生丰富的低频泛音(贝斯的"冲击感"),衰减后I值降低,只剩温暖的基频。
音色变形是在两个预设音色之间平滑过渡——通过线性插值各参数实现:
param_out = param_A × (1-morph) + param_B × morph
morph从0到1,音色从A平滑过渡到B。这是实时音色演变的强大工具。
Yamaha DX7拥有数万个用户创建的音色,形成了庞大的音色数据库。分析这些音色可以发现规律:
音色设计师 — 掌握FM音色设计的系统方法,建立预设音色库,理解音色变形技术!FM合成阶段完成,你已能设计各种FM音色!
// 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验证通过
让我们完整走一遍音色设计流程,从分析到实现:
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无结构体,用多个数组代替)
// 每个音色 = 算法 + 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
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
// 支持运行时替换音色
两个音色之间的平滑过渡:
// 线性插值
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))