2算子FM是FM合成的最基本形式,也是理解更复杂FM架构的基础。Yamaha OPL系列(用于Sound Blaster声卡)的核心就是2算子FM。本课实现完整的2算子FM合成器,包含调制器反馈和算法选择。
FM合成中的"算子"是一个正弦波振荡器 + 包络发生器的组合单元。每个算子可以:
算法0(串行FM):
Mod → Carrier → 输出
调制器调制载波频率,产生丰富泛音
算法1(并行叠加):
Mod → 输出
Carrier → 输出
两个算子独立输出,叠加为更厚的音色
串行FM是"真正的"FM合成,产生非谐波泛音。并行叠加只是两个正弦波的叠加,等价于加法合成。
| 芯片 | 算子数 | 通道数 | 算法 | 应用 |
|---|---|---|---|---|
| YM3526 (OPL) | 2 | 9 | 串行/并行 | 早期声卡 |
| YM3812 (OPL2) | 2 | 9 | 串行/并行 | Adlib/SB |
| YMF262 (OPL3) | 4 | 18 | 4种 | SB16 |
| YM2151 (OPM) | 4 | 8 | 8种 | X68000 |
| YM2612 (OPN2) | 4 | 6 | 8种 | Mega Drive |
调制器的输出反馈到自身输入,形成自调制(Self-modulation)。这是2算子FM的重要增强:
| 音色 | C:M比 | I值 | 反馈 | 说明 |
|---|---|---|---|---|
| 电钢琴 | 1:1 | 1-2 | 0 | 温暖柔和 |
| 贝斯 | 1:1 | 3-5 | 0-1 | 厚重低音 |
| 铜管 | 1:1 | 5-7 | 0 | 明亮有力 |
| 钟 | 1:3.5 | 3 | 0 | 非谐波,金属感 |
| 笛子 | 1:1 | 0.5 | 0 | 接近纯音 |
| 噪声鼓 | 1:1 | 10+ | 7 | 高反馈+高I |
FM实践者 — 实现2算子FM合成器,掌握串行/并行算法,理解调制器反馈的效果,能设计基本FM音色!
// 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验证通过
Yamaha YM3812(OPL2)是PC游戏音乐的里程碑:
| 游戏 | 年份 | 音乐特色 |
|---|---|---|
| Doom | 1993 | Bobby Prince的FM金属音色 |
| 德军总部3D | 1992 | 紧张的FM氛围音乐 |
| 猴岛小英雄 | 1990 | Michael Land的幽默FM配乐 |
| SimCity 2000 | 1993 | 轻松的Jazz FM风格 |
| Duke Nukem 3D | 1996 | 标志性的FM吉他和贝斯 |
OPL2的9个2算子通道在熟练的作曲家手中能产生令人惊叹的音乐。关键在于每个通道的参数配置——同样的硬件,不同的参数产生完全不同的音色。
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的音色通过寄存器写入配置,每个通道9组寄存器:
$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的限制有助于我们正确使用它:
尽管有这些限制,OPL2仍然创造了无数经典游戏音乐。关键在于作曲家如何巧妙利用2算子的简洁性。
以下是OPL2游戏中常用的音色参数,可以直接用于我们的2算子FM模块:
| 音色名 | 算法 | C:M | I值 | 反馈 |
|---|---|---|---|---|
| Doom吉他 | 串行 | 1:1 | 6 | 3 |
| Duke贝斯 | 串行 | 1:1 | 8 | 1 |
| 猴岛长笛 | 串行 | 1:1 | 1 | 0 |
| SimCity钢琴 | 并行 | 1:1 | 2 | 0 |
| Wolf弦乐 | 串行 | 1:1 | 1 | 1 |