4算子FM是专业FM合成的标准配置,Yamaha OPN2(Mega Drive/Genesis)和OPM(X68000)都使用4算子架构。4个算子可以组合出8种算法,从简单的串行调制到复杂的混合拓扑,音色可能性指数级增长。
算法0:全串行
OP1→OP2→OP3→OP4→输出
最强调制链,音色最复杂
算法1:双串行
OP1→OP2→输出
OP3→OP4→输出
两个独立FM通道叠加
算法2:串行+并行
OP1→OP2→输出
OP3→输出
OP4→输出
一个FM+两个正弦
算法3:混合1
OP1→┐
→OP3→输出
OP2→┘ OP4→输出
双调制器+独立载波
算法4:混合2
OP1→OP2→输出
OP3→OP4→输出(并联)
两个FM通道
算法5:双并行调制
OP1→┐
→OP3→输出
OP2→┘
OP4→输出
类似算法3但结构不同
算法6:三调制器
OP1→┐
OP2→→OP4→输出
OP3→┘
三重调制,丰富泛音
算法7:全并行
OP1→输出
OP2→输出
OP3→输出
OP4→输出
纯加法合成
Sega Mega Drive使用的YM2612(OPN2)拥有6个4算子FM通道,是游戏音乐史上的里程碑:
| 特征 | 2算子 | 4算子 |
|---|---|---|
| 算法数 | 2 | 8 |
| 最大调制链 | 1级 | 3级 |
| 边带复杂度 | 中等 | 极高 |
| 电钢琴模拟 | 基本 | 优秀 |
| 铜管模拟 | 一般 | 逼真 |
| 弦乐模拟 | 困难 | 可行 |
| 硬件资源 | 2个正弦表+2个乘法器 | 4个正弦表+多个乘法器 |
4算子架构师 — 实现4算子FM合成器,理解8种算法拓扑,掌握算子路由与输出混合!
// fm_4op.v - 4算子FM合成器
// 支持8种算法配置(类似Yamaha OPN2/OPM)
module fm_4op #(
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 [2:0] algorithm, // 算法选择(0-7)
// 4个算子的频率和调制指数
input wire [PHASE_BITS-1:0] op_freq [0:3],
input wire [11:0] op_mod_index [0:3],
input wire [3:0] op_feedback [0:3],
input wire [BIT_DEPTH-1:0] op_level [0:3],
output wire [BIT_DEPTH-1:0] audio_out
);
// 正弦查找表
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
// 4个算子的相位累加器
reg [PHASE_BITS-1:0] op_phase [0:3];
reg signed [BIT_DEPTH:0] op_output_signed [0:3];
reg signed [BIT_DEPTH:0] op_feedback_reg [0:3];
integer j;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (j = 0; j < 4; j = j + 1) begin
op_phase[j] <= {PHASE_BITS{1'b0}};
op_output_signed[j] <= {BIT_DEPTH+1{1'b0}};
op_feedback_reg[j] <= {BIT_DEPTH+1{1'b0}};
end
end else begin
for (j = 0; j < 4; j = j + 1) begin
op_phase[j] <= op_phase[j] + op_freq[j];
end
end
end
// 算法路由:根据algorithm决定调制关系
// 计算每个算子的调制输入
reg signed [BIT_DEPTH+11:0] mod_input [0:3];
always @(*) begin
// 默认无调制
for (j = 0; j < 4; j = j + 1)
mod_input[j] = {BIT_DEPTH+12{1'b0}};
case (algorithm)
3'd0: begin // OP1→OP2→OP3→OP4 (全串行)
mod_input[0] = op_feedback_reg[0] * $signed({1'b0, op_feedback[0], 4'd0});
mod_input[1] = op_output_signed[0] * $signed({1'b0, op_mod_index[1]});
mod_input[2] = op_output_signed[1] * $signed({1'b0, op_mod_index[2]});
mod_input[3] = op_output_signed[2] * $signed({1'b0, op_mod_index[3]});
end
3'd1: begin // OP1→OP2, OP3→OP4 (双串行)
mod_input[0] = op_feedback_reg[0] * $signed({1'b0, op_feedback[0], 4'd0});
mod_input[1] = op_output_signed[0] * $signed({1'b0, op_mod_index[1]});
mod_input[2] = op_feedback_reg[2] * $signed({1'b0, op_feedback[2], 4'd0});
mod_input[3] = op_output_signed[2] * $signed({1'b0, op_mod_index[3]});
end
3'd2: begin // OP1→OP2, OP3+OP4并行
mod_input[0] = op_feedback_reg[0] * $signed({1'b0, op_feedback[0], 4'd0});
mod_input[1] = op_output_signed[0] * $signed({1'b0, op_mod_index[1]});
mod_input[2] = op_feedback_reg[2] * $signed({1'b0, op_feedback[2], 4'd0});
mod_input[3] = op_feedback_reg[3] * $signed({1'b0, op_feedback[3], 4'd0});
end
3'd3: begin // OP1+OP2→OP3, OP4独立
mod_input[0] = op_feedback_reg[0] * $signed({1'b0, op_feedback[0], 4'd0});
mod_input[1] = op_feedback_reg[1] * $signed({1'b0, op_feedback[1], 4'd0});
mod_input[2] = (op_output_signed[0] + op_output_signed[1]) * $signed({1'b0, op_mod_index[2]});
mod_input[3] = op_feedback_reg[3] * $signed({1'b0, op_feedback[3], 4'd0});
end
default: begin // 全并行
for (j = 0; j < 4; j = j + 1)
mod_input[j] = op_feedback_reg[j] * $signed({1'b0, op_feedback[j], 4'd0});
end
endcase
end
// 计算每个算子输出
wire [SIN_ADDR_BITS-1:0] op_addr [0:3];
wire [BIT_DEPTH-1:0] op_sin_val [0:3];
genvar g;
generate
for (g = 0; g < 4; g = g + 1) begin : gen_op
assign op_addr[g] = (op_phase[g] + mod_input[g][PHASE_BITS-1 -: SIN_ADDR_BITS])
[SIN_ADDR_BITS-1:0];
assign op_sin_val[g] = sin_table[op_addr[g]];
end
endgenerate
// 更新输出和反馈
integer k;
always @(posedge clk) begin
for (k = 0; k < 4; k = k + 1) begin
op_output_signed[k] <= $signed({1'b0, op_sin_val[k]}) - 128;
op_feedback_reg[k] <= $signed({1'b0, op_sin_val[k]}) - 128;
end
end
// 输出混合:根据算法选择哪些算子作为载波输出
reg signed [BIT_DEPTH+3:0] mix_out;
always @(*) begin
case (algorithm)
3'd0: mix_out = op_output_signed[3] * $signed({1'b0, op_level[3]});
3'd1: mix_out = (op_output_signed[1] * $signed({1'b0, op_level[1]}) +
op_output_signed[3] * $signed({1'b0, op_level[3]})) >>> 1;
3'd2: mix_out = (op_output_signed[1] * $signed({1'b0, op_level[1]}) +
op_output_signed[2] * $signed({1'b0, op_level[2]}) +
op_output_signed[3] * $signed({1'b0, op_level[3]})) >>> 2;
3'd3: mix_out = (op_output_signed[2] * $signed({1'b0, op_level[2]}) +
op_output_signed[3] * $signed({1'b0, op_level[3]})) >>> 1;
default: mix_out = (op_output_signed[0] * $signed({1'b0, op_level[0]}) +
op_output_signed[1] * $signed({1'b0, op_level[1]}) +
op_output_signed[2] * $signed({1'b0, op_level[2]}) +
op_output_signed[3] * $signed({1'b0, op_level[3]})) >>> 2;
endcase
end
assign audio_out = mix_out[BIT_DEPTH+2:BIT_DEPTH-5] + 128;
endmodule
✅ Verilator验证通过
YM2612(OPN2)是Mega Drive/Genesis的核心声音芯片,6个4算子FM通道:
| 通道 | 特性 | 典型用途 |
|---|---|---|
| CH1-5 | 4算子FM,8种算法 | 旋律/和声/贝斯 |
| CH6 | 4算子FM 或 DAC模式 | 采样鼓声 |
CH6的DAC模式可以播放8位PCM采样——这让Mega Drive可以使用真实的鼓声采样,而其他通道继续播放FM合成音乐。Sonic系列就是这种混合方案的典范。
在硬件中实现4算子FM需要考虑时序:
4个算子的包络需要协调设计:
// 经典铜管音色的4算子包络
OP1(调制器): 快Attack, 中Decay, 高Sustain → 持续的丰富泛音
OP2(载波): 快Attack, 中Decay, 高Sustain → 持续的音量
OP3(调制器): 快Attack, 快Decay, 低Sustain → 起音时的额外泛音
OP4(载波): 快Attack, 快Decay, 低Sustain → 起音时的额外"冲击"
// 关键原则:
// 调制器包络决定音色随时间的变化
// 载波包络决定音量随时间的变化
// 调制器包络通常比载波短——起音时泛音丰富,衰减后变柔和
Mega Drive的6个FM通道如何分配是编曲的关键决策:
| 通道 | 算法 | 音色 | 说明 |
|---|---|---|---|
| FM1 | 1(双串行) | 主旋律 | 50%方波+FM泛音 |
| FM2 | 7(并行) | 和声 | 2-3个正弦叠加 |
| FM3 | 0(全串行) | 特殊音效 | 金属/钟声效果 |
| FM4 | 1(双串行) | 贝斯 | 低频+高I值 |
| FM5 | 2(混合) | Pad/背景 | 柔和持续音 |
| FM6 | DAC模式 | 鼓采样 | 8位PCM回放 |
Sonic系列的经典配乐是FM合成的教科书级范例: