第3课:三角波

阶段1:波形基础

三角波是芯片音乐中另一个关键波形。NES的2A03芯片专门有一个三角波通道用于低音线(Bass Line),它的声音比方波更柔和、更接近正弦波,但又带有独特的数字味道。

三角波的数学特性

三角波可以看作方波经过积分得到的结果——它线性上升再线性下降,形成一个三角形。其傅里叶级数为:

f(t) = (8A/π²) × [sin(ωt) - sin(3ωt)/9 + sin(5ωt)/25 - ...]

关键特征:

🔊 为什么NES用三角波做低音?

NES的三角波通道有几个独特设计:

经典案例:《超级玛丽》地下关卡的低音线就是三角波通道演奏的。

相位到波形的映射

生成三角波的关键是将线性递增的相位映射为"先上后下"的波形:

📐 映射原理

相位范围:0 → 2^N-1,一个完整周期

前半周期(0 到 2^(N-1)-1):输出从0线性上升到最大值
    output = phase[N-2:0]  (取低N-1位)
    
后半周期(2^(N-1) 到 2^N-1):输出从最大值线性下降到0
    output = ~phase[N-2:0]  (取反)

判断前半/后半周期的方法:检查相位的最高位。最高位=0是前半周期(上升),最高位=1是后半周期(下降)。

Verilog实现解析

相位累加器

与方波相同,使用32位相位累加器。32位提供极高的频率精度——在50MHz时钟下,最小频率分辨率约为0.012Hz,远超音乐需求。

相位截断

32位相位太长,不能直接用于波形映射。我们取相位的高BIT_DEPTH+1位(共9位):

wire [BIT_DEPTH:0] phase_top = phase[31 -: 9];

这9位中:最高位表示上升/下降,低8位表示在当前半周期中的位置。

波形生成

根据最高位选择:上升时直接输出ramp值,下降时输出ramp的按位取反。取反操作在硬件中零成本(布线即可)。

🔬 频率计算示例

32位相位,50MHz时钟:

// C3 (低音C) = 130.81Hz
freq_tune = round(130.81 * 2^32 / 50000000) = round(11242) = 11242
// 实际频率 = 11242 * 50000000 / 4294967296 = 130.81Hz ✅

// A2 (低音A) = 110Hz  
freq_tune = round(110 * 2^32 / 50000000) = round(9449) = 9449
// 实际频率 = 9449 * 50000000 / 4294967296 = 110.00Hz ✅

32位相位的精度在可听范围内完全够用。

三角波 vs 方波对比

特征方波三角波
谐波奇次谐波,1/n衰减奇次谐波,1/n²衰减
音色明亮、锋利、有攻击性柔和、温暖、圆润
典型用途主旋律、领奏低音线、铺底
NES音量控制4级无(固定音量)
硬件复杂度比较器即可需要加减运算
占空比可变固定50%
三角波的"线性"特性使它在视觉上也很直观——你可以直接在示波器或波形查看器中看到完美的三角形。在VCD波形中,三角波看起来像锯齿交替上升下降,非常容易识别。
  1. 用Verilator编译三角波模块,写testbench验证输出波形
  2. 在同一testbench中同时例化方波和三角波,对比两者波形
  3. 模拟NES的32级阶梯三角波:将8位输出截断为5位(32级),再扩展回8位,听感有何变化?
  4. 挑战:实现一个"变形"波形,通过参数控制从三角波平滑过渡到方波

波形塑形者 — 掌握三角波生成原理,理解相位到幅度的映射方法,能对比分析不同波形的频谱特征!

Verilog 实现

triangle_wave.v
// triangle_wave.v - 三角波发生器
// 使用相位累加器 + 线性映射生成三角波
module triangle_wave #(
    parameter CLK_FREQ  = 50000000,
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [PHASE_BITS-1:0] freq_tune,
    output wire [BIT_DEPTH-1:0] wave_out
);
    reg [PHASE_BITS-1:0] phase;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            phase <= {PHASE_BITS{1'b0}};
        else
            phase <= phase + freq_tune;
    end
    
    // 取相位高BIT_DEPTH+1位用于波形映射
    // 最高位决定上升/下降,其余位决定幅度
    wire [BIT_DEPTH:0] phase_top;
    assign phase_top = phase[PHASE_BITS-1 -: (BIT_DEPTH+1)];
    
    // 最高位=0时上升(0→max),最高位=1时下降(max→0)
    wire [BIT_DEPTH-1:0] ramp;
    assign ramp = phase_top[BIT_DEPTH-1:0];
    
    assign wave_out = phase_top[BIT_DEPTH] ? (~ramp) : ramp;
endmodule

✅ Verilator验证通过

NES三角波的硬件实现

NES的三角波通道有一个独特的设计——它的三角波不是数学上精确的,而是由32级阶梯近似:

📐 NES三角波的32级阶梯

周期内32个位置对应的输出值:
位置  0-15:  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15
位置 16-31: 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0

实际DAC输出(7位线性):
0→15→0,每级增量 = 127/15 ≈ 8.47
量化后: 0, 8, 17, 25, 34, 42, 51, 59, 68, 76, 85, 93, 102, 110, 119, 127

这种32级离散阶梯在低频时可以听到明显的"台阶"失真,但在中高频时人耳感知为平滑的三角波。

三角波通道的隐藏功能

NES三角波通道有两个特殊的控制位:

🔬 三角波的低音设计技巧

在芯片音乐中用三角波做低音有几种经典技巧:

  1. 八度跳跃:在节拍上跳到高八度再回来,增加节奏感
  2. 快速交替:三角波+方波快速交替,产生更厚的低音
  3. 节奏型低音:不是持续的长音,而是按节奏断开(通过开/关通道实现)
  4. 滑音低音:音符之间用滑音连接,产生流畅的贝斯线

三角波的正弦波近似

在FM合成中我们经常需要正弦波,但正弦波查找表占用ROM。三角波可以作为正弦波的粗略近似——它们的奇次谐波结构相似,只是三角波的高次谐波衰减更快。在精度要求不高的场合,三角波可以替代正弦波。

谐波正弦波三角波方波
1次(基频)1.0001.0001.000
3次0.000-0.1110.333
5次0.0000.0400.200
7次0.000-0.0200.143
9次0.0000.0120.111

三角波的数学推导

三角波的傅里叶级数展开是理解其频谱的关键:

📐 完整推导

三角波(振幅A,周期T):
f(t) = (8A/π²) × Σ[(-1)^((n-1)/2) / n²] × sin(2πnt/T)

其中 n = 1, 3, 5, 7, ...(奇次谐波)

各次谐波振幅:
1次: 8A/π² ≈ 0.811A
3次: -8A/(9π²) ≈ -0.090A
5次: 8A/(25π²) ≈ 0.032A  
7次: -8A/(49π²) ≈ -0.017A

对比方波:
1次: 4A/π ≈ 1.273A
3次: 4A/(3π) ≈ 0.424A
5次: 4A/(5π) ≈ 0.255A

→ 三角波的3次谐波仅为基频的11%
→ 方波的3次谐波为基频的33%
→ 三角波高次谐波衰减快3倍

三角波通道的实用编程技巧

NES作曲家使用三角波通道的技巧:

波形选择器:多波形统一接口

在实际系统中,需要一个模块根据选择信号输出不同波形:

🔧 多波形选择器实现

module wave_select #(
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32
)(
    input  wire [PHASE_BITS-1:0] phase,
    input  wire [1:0] wave_type,    // 00=方波,01=三角,10=锯齿,11=噪声
    input  wire [1:0] duty,         // 方波占空比
    output wire [BIT_DEPTH-1:0] wave_out
);

reg [BIT_DEPTH-1:0] out;
always @(*) begin
    case (wave_type)
        2'b00: begin // 方波
            case (duty)
                2'b00: out = (phase[31:28] < 4'd2)  ? 8'd255 : 8'd0;
                2'b01: out = (phase[31:28] < 4'd4)  ? 8'd255 : 8'd0;
                2'b10: out = phase[31] ? 8'd0 : 8'd255;
                2'b11: out = (phase[31:28] < 4'd12) ? 8'd255 : 8'd0;
            endcase
        end
        2'b01: out = phase[31] ? ~phase[30:23] : phase[30:23];
        2'b10: out = phase[31:24];
        2'b11: out = {phase[31],phase[29],phase[27],phase[25],
                      phase[23],phase[21],phase[19],phase[17]};
        default: out = 8'd0;
    endcase
end
assign wave_out = out;
endmodule

三角波的低音编程技巧

三角波在芯片音乐中最常用的角色是低音线(Bass Line)。以下是一些编程技巧:

🎵 低音线节奏模式

// 模式1: 每拍一个根音(最基础)
|C---|C---|C---|C---|

// 模式2: 八度跳跃(更有律动)
|C-c-|C-c-|C-c-|C-c-|  (c=高八度C)

// 模式3: 行走贝斯(Walking Bass)
|C-E-|F-G-|A-F-|E-C-|

// 模式4: 断奏(Staccato)
|C.C.|C.C.|C.C.|C.C.|  (.=休止)

// 模式5: 节奏型低音
|C--C|C--C|C-CC|C---|

三角波低音的魅力在于其"干净"——没有方波那样的高频泛音干扰,低音线条清晰可辨。