第20课:多通道混音

阶段4:音序器

多通道混音是将多个独立声源合并为单一输出的过程。在芯片音乐中,这通常只是简单的算术相加,但需要处理音量控制、防溢出和信号对齐等问题。本课实现4通道混音器,为最终的完整音序器做准备。

数字混音原理

混音的本质就是加法:

output = ch1 + ch2 + ch3 + ch4

但有几个关键问题需要解决:

🔧 混音的三大问题

  1. 溢出:4个255相加=1020,超出8位范围。需要归一化或裁剪
  2. 有符号vs无符号:音频信号通常以有符号数表示(-128到+127),方便计算
  3. 音量控制:每个通道独立音量+主音量,需要两级乘法

NES的混音方式

NES 2A03的混音非常特殊——它不是简单的加法!

通道权重说明
方波1主旋律
方波2副旋律/和声
三角波低音线
噪声鼓/打击乐
DPCM采样

NES内部使用非线性混合——方波通道的权重最高,三角波次之。这就是为什么NES音乐中三角波低音总是"退居幕后"。

混音策略对比

策略公式优点缺点
简单相加sum = a+b+c+d简单易溢出
平均sum = (a+b+c+d)/4不溢出音量降低
裁剪sum = clamp(a+b+c+d)保持响度失真
加权求和sum = Σ(wi×xi)/Σwi灵活需要乘法

防止数字削波(Clipping)

当信号超出范围时产生削波失真——波形顶部/底部被"切平",听起来刺耳且粗糙。虽然削波在某些场景下是有意为之(吉他过载),但通常需要避免。

芯片音乐中的"削波"其实是风格的一部分!8位系统的动态范围本来就有限,多个通道叠加时的轻微削波反而增加了温暖感和"粗糙"感。这就是为什么NES音乐听起来总是"满血"的——混音经常接近0dBFS。

Verilog实现解析

我们的混音器采用加权求和方案:

  1. 每个通道的输入转为有符号数(-128到+127)
  2. 乘以通道独立音量
  3. 所有通道求和
  4. 乘以主音量
  5. 右移归一化(避免除法器)
  6. 裁剪到输出范围
  1. 实现4通道混音器,测试不同通道组合
  2. 验证:4通道全开且满幅时,输出不溢出
  3. 实验:对比裁剪vs归一化的听感差异
  4. 挑战:实现动态范围压缩——自动降低主音量防止削波

混音工程师 — 实现多通道混音器,掌握数字混音原理,理解溢出保护和信号归一化!阶段4完成,音序器系统全部就绪!

Verilog 实现

channel_mixer.v
// channel_mixer.v - 多通道混音器
// 4通道音频混合 + 主音量控制
module channel_mixer #(
    parameter BIT_DEPTH = 8,
    parameter NUM_CHANNELS = 4
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [BIT_DEPTH-1:0] channel_in [0:NUM_CHANNELS-1],
    input  wire [BIT_DEPTH-1:0] channel_vol [0:NUM_CHANNELS-1],
    input  wire [BIT_DEPTH-1:0] master_vol,
    input  wire [NUM_CHANNELS-1:0] channel_enable,
    output wire [BIT_DEPTH-1:0] mixed_out
);
    // 每通道:乘以音量后累加
    reg signed [BIT_DEPTH+8:0] mix_sum;
    
    integer i;
    always @(*) begin
        mix_sum = {(BIT_DEPTH+9){1'b0}};
        for (i = 0; i < NUM_CHANNELS; i = i + 1) begin
            if (channel_enable[i]) begin
                // 转为有符号
                mix_sum = mix_sum + 
                    ($signed({1'b0, channel_in[i]}) - 128) * 
                    $signed({1'b0, channel_vol[i]});
            end
        end
    end
    
    // 主音量控制
    wire signed [BIT_DEPTH+16:0] with_master;
    assign with_master = mix_sum * $signed({1'b0, master_vol});
    
    // 归一化:除以通道数和音量范围,防止溢出
    wire signed [BIT_DEPTH+4:0] normalized;
    assign normalized = with_master >>> (8 + 2); // >> (8位音量 + 2位通道补偿)
    
    // 裁剪到0-255范围
    assign mixed_out = (normalized > 127) ? 8'd255 :
                       (normalized < -128) ? 8'd0 :
                       normalized[BIT_DEPTH-1:0] + 128;
endmodule

✅ Verilator验证通过

数字混音进阶技术

1. 平衡控制(Pan)

立体声混音需要左右平衡控制:

// 简单的左右平衡
wire [7:0] left_out  = (audio * (255 - pan)) >> 8;
wire [7:0] right_out = (audio * pan) >> 8;
// pan=0: 全左, pan=128: 中间, pan=255: 全右

2. 发送效果(Send/Return)

将部分信号发送到效果处理器(如延迟、混响),再返回混音:

// 简单延迟效果
reg [7:0] delay_buffer [0:DELAY_LEN-1];
wire [7:0] delayed = delay_buffer[delay_ptr];
always @(posedge clk) begin
    delay_buffer[delay_ptr] <= audio_in;
    delay_ptr <= delay_ptr + 1;
end
// 混合干/湿信号
wire [7:0] with_delay = audio_in + (delayed * send_level >> 8);

3. 动态范围压缩

自动控制音量防止削波:

// 简单压缩器
reg [7:0] gain_reduction;
always @(posedge clk) begin
    if (audio_out > THRESHOLD)
        gain_reduction <= gain_reduction + 1;  // 降低增益
    else if (gain_reduction > 0)
        gain_reduction <= gain_reduction - 1;  // 恢复增益
end
wire [7:0] compressed = (audio_in * (255 - gain_reduction)) >> 8;

NES混音的硬件实现

NES 2A03的混音不是简单的加法,而是非线性加权求和

📐 NES DAC输出公式

// NES混音公式(近似)
// 方波通道: out_sq = 95.52 / (8128/(sq1+sq2) + 100)
// 三角波: 直接DAC输出(固定振幅)
// 噪声: 同方波公式但权重不同
// DPCM: 同噪声

// 关键特征:
// 1. 方波通道之间有非线性交互
// 2. 两个方波同时播放时,单个方波的音量会下降
// 3. 这产生了"抢频"效果——通道越多,每个通道越轻

阶段4总结:音序器

📚 音序器阶段回顾

音序器阶段完成了从"手动演奏"到"自动编曲"的跨越。现在你可以编程一段完整的芯片音乐,让FPGA自动播放!

专业混音的基本原则

即使只有4个通道,也需要遵循混音基本原则:

🔧 混音五原则

  1. 频段分离:每个通道占据不同频段(低音→低频,旋律→高频)
  2. 音量平衡:没有通道应该明显盖过其他通道
  3. 空间感:通过延迟和相位制造深度
  4. 动态对比:安静和响亮交替,避免持续满音量
  5. 频谱清洁:同一时刻不要有太多通道在同一频段竞争

4通道的频率分配策略

通道频率范围主要频段混音权重
三角波低音50-400Hz低频中(固定音量)
方波和声200-2000Hz中频
方波旋律400-4000Hz中高频
噪声鼓全频段宽频低-中

当旋律和和声在同一频段时,可以通过占空比差异来分离——50%方波(饱满) vs 12.5%方波(薄),即使同频段也不会完全重叠。

混音器的FPGA资源优化

4通道混音器在低端FPGA上的优化策略:

🔧 资源优化方案

优化资源节省音质影响
4位音量替代8位3个乘法器→3个移位器16级音量(可接受)
时分复用乘法器4个乘法器→1个需要4倍时钟
查表替代乘法0个乘法器64KB ROM
加法替代乘法0个乘法器音量只有几级
1位DAC直接输出最简需要外部模拟滤波

对于我们的教学目的,使用4个独立乘法器是最清晰的方案。在实际产品中,需要根据FPGA资源做权衡。

📐 混音器输出范围计算

4通道混音时的数值范围分析:

// 最坏情况:4个通道同时输出最大值
// 各通道输出范围:0-255(8位无符号)
// 转有符号:-128到+127
// 4通道求和:-512到+508 → 需要10位

// 乘以通道音量(0-255):
// 最大值:127×255 = 32,385 → 需要16位
// 4通道求和:最大129,540 → 需要17位

// 乘以主音量(0-255):
// 最大值:129,540×255 ≈ 33,032,700 → 需要26位

// 归一化后裁剪到8位:
// 需要精心设计右移位数确保音量适中
// 推荐右移量:8(音量) + 2(通道补偿) = 10位

混音器的专业级实现

从教学级到专业级混音器的升级路径:

🔧 专业级混音器功能

  1. 每通道Pan控制:左右平衡,8位精度
  2. 每通道发送效果:将信号发送到延迟/混响总线
  3. 辅助总线:独立的混音总线用于效果处理
  4. 主总线压缩:自动防止削波
  5. VU表:实时音量指示
  6. 通道Solo/Mute:单独监听/静音
// 完整混音器架构
// 每通道处理链:
// input → volume → pan(L/R) → send(Aux1/Aux2) → fader → mix bus
//
// 输出链:
// mix bus → compressor → master fader → limiter → output
//
// 辅助链:
// aux1 bus → delay effect → return to mix
// aux2 bus → reverb effect → return to mix