滑音(Portamento/Glide)和弯音(Pitch Bend)让音符之间的过渡更加流畅和富有表现力。滑音是两个音符之间的平滑滑动,弯音是实时的音高偏移——两者都是芯片音乐的标志性效果。
滑音是从一个音高平滑过渡到另一个音高,中间经过所有频率。效果类似于手指在吉他弦上滑动。
NES 2A03的方波通道内置Sweep(扫描)单元:
| 参数 | 位宽 | 说明 |
|---|---|---|
| Enable | 1位 | 开启/关闭扫描 |
| Period | 3位 | 扫描速率(0-7) |
| Negate | 1位 | 方向:0=向上扫描(频率增加),1=向下扫描(频率降低) |
| Shift | 3位 | 扫描量(右移位数) |
Sweep的扫描公式:
// 每个sweep周期:
if (negate)
new_freq = current_freq - (current_freq >> shift)
else
new_freq = current_freq + (current_freq >> shift)
这种"右移"扫描是乘法器的廉价替代——移位操作在硬件中零成本。但注意:向上扫描可能让频率增加到溢出,NES会在这种情况下静音该通道。
弯音是实时的音高偏移,通常由MIDI弯音轮控制。标准范围是±2半音,但可以配置为更宽。
MIDI弯音消息:
在硬件中,弯音偏移量 = pitch_bend × base_freq × sensitivity / 8192
为避免除法,我们用移位近似:offset ≈ pitch_bend × base_freq >> N
硬件中实现滑音有两种方式:
每个时间步增加/减少固定值。实现简单,但听感上高音区滑得慢、低音区滑得快(因为音高是指数关系)。
每个时间步增加/减少当前频率的固定比例(乘法)。听感上所有音区滑音速度一致。NES的Sweep就是指数滑音。
流畅演奏者 — 掌握滑音和弯音的原理与实现,理解线性和指数滑音的差异,实现MIDI弯音控制!阶段2完成,你已能控制音调的所有维度!
// portamento.v - 滑音与弯音模块
// portamento: 平滑音高过渡
// pitch_bend: 实时音高偏移
module portamento #(
parameter PHASE_BITS = 32,
parameter GLIDE_BITS = 16,
parameter CLK_FREQ = 50000000
)(
input wire clk,
input wire rst_n,
input wire [PHASE_BITS-1:0] target_freq, // 目标频率调谐字
input wire [GLIDE_BITS-1:0] glide_rate, // 滑音速率
input wire glide_enable, // 滑音使能
input wire signed [13:0] pitch_bend, // 弯音偏移(有符号)
input wire bend_enable, // 弯音使能
output wire [PHASE_BITS-1:0] output_freq // 输出频率
);
// 滑音:从当前频率平滑过渡到目标频率
reg [PHASE_BITS-1:0] current_freq;
reg [GLIDE_BITS-1:0] glide_counter;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_freq <= {PHASE_BITS{1'b0}};
glide_counter <= {GLIDE_BITS{1'b0}};
end else begin
glide_counter <= glide_counter + 1'b1;
if (glide_enable && glide_counter >= glide_rate) begin
glide_counter <= {GLIDE_BITS{1'b0}};
if (current_freq < target_freq)
current_freq <= current_freq + 1'b1;
else if (current_freq > target_freq)
current_freq <= current_freq - 1'b1;
// 到达目标时不变化
end else if (!glide_enable) begin
// 滑音禁用时直接跳到目标
current_freq <= target_freq;
end
end
end
// 弯音:在当前频率上叠加偏移
// pitch_bend范围:-8192到+8191(14位有符号)
// 映射为频率偏移:±2半音(标准MIDI弯音范围)
wire signed [PHASE_BITS:0] bend_offset;
// 弯音比例因子:2半音 ≈ base_freq × 0.122
// 简化:bend_offset = pitch_bend × base_freq >> 16
wire signed [PHASE_BITS:0] bend_product;
assign bend_product = $signed(pitch_bend) * $signed({1'b0, current_freq[31:15]});
assign bend_offset = bend_product >> 10;
wire signed [PHASE_BITS:0] freq_with_bend;
assign freq_with_bend = $signed({1'b0, current_freq}) +
(bend_enable ? bend_offset : {PHASE_BITS+1{1'b0}});
// 确保输出为正
assign output_freq = (freq_with_bend[PHASE_BITS-1:0] > 0) ?
freq_with_bend[PHASE_BITS-1:0] : current_freq;
endmodule
✅ Verilator验证通过
NES的Sweep单元有一些微妙的硬件行为,很多模拟器都实现错了:
new = current + (~current >> shift) + 1,不是简单的减法滑音不仅是技术特性,更是创作工具:
如果连接MIDI弯音轮,需要处理以下问题:
// MIDI弯音消息解析
// Pitch Bend Change: 0xEc ll mm
// c = channel, ll = LSB(7bit), mm = MSB(7bit)
// 合并为14位值: value = (mm << 7) | ll
// 范围: 0-16383, 中心=8192
wire signed [13:0] pitch_bend;
assign pitch_bend = midi_bend_value - 14'd8192; // -8192 to +8191
// 转换为频率偏移
// ±2半音 ≈ ±12%频率变化
// offset = pitch_bend × base_freq × 12% / 8192
wire signed [31:0] bend_offset;
assign bend_offset = (pitch_bend * base_freq) >>> 16; // 近似
阶段2的5课让我们从"静态波形"进化到"有表现力的音乐":
这些模块组合起来,让一个简单的方波变成了可以演奏富有表情的音乐的"乐器"。
完整的MIDI弯音处理流程:
// Step 1: 接收MIDI Pitch Bend消息
// 格式: 0xEc ll mm (c=通道, ll=LSB, mm=MSB)
midi_value = (msb << 7) | lsb; // 0-16383
center = 8192;
// Step 2: 转为有符号偏移
bend_signed = midi_value - center; // -8192 to +8191
// Step 3: 缩放到半音偏移
// ±2半音范围: bend_semitones = bend_signed × 2 / 8192
semitones = bend_signed * bend_range / 8192;
// Step 4: 半音到频率倍率
// 每半音 = 2^(1/12) ≈ 1.0595
// freq_ratio = 2^(semitones/12)
// Step 5: 应用到基础频率
// final_freq = base_freq × freq_ratio
// 用查找表或近似计算避免实时幂运算
多个声部同时滑音时,需要确保它们以不同速率到达目标,否则声音会"糊"在一起:
// 各声部使用不同的滑音速率
melody_glide_rate = 8'd10; // 快速滑音
bass_glide_rate = 8'd40; // 慢速滑音(更浑厚)
harmony_glide_rate = 8'd20; // 中速滑音
滑音时间对听感的影响比想象中更微妙:
| 时间 | 效果 | 音乐风格 |
|---|---|---|
| <10ms | 几乎听不到滑音 | — |
| 10-50ms | 轻微连奏感 | 断奏过渡 |
| 50-150ms | 明显滑音 | 标准连奏 |
| 150-300ms | 显著滑动 | 表现力强的旋律 |
| 300-500ms | 戏剧性滑音 | Acid House, 特效 |
| >500ms | 极端效果 | 科幻音效 |