第19课:鼓机

阶段4:音序器

鼓机是芯片音乐节奏的核心。本课实现一个完整的4音色鼓机——底鼓、军鼓、踩镲、开镲,每种音色用不同的合成方法生成,配合16步序列器自动演奏节奏模式。

芯片鼓音色的合成方法

与真实鼓不同,芯片音乐的鼓完全是合成的。每种鼓有独特的合成策略:

🔊 四种鼓音色解析

合成方法关键参数时长
底鼓(Kick)正弦波+频率扫描起始~150Hz→结束~50Hz200-400ms
军鼓(Snare)噪声+三角波噪声带宽+音高100-200ms
踩镲(Hi-hat)带通滤波噪声截止频率高30-80ms
开镲(Open Hat)同踩镲但衰减慢截止频率高200-500ms

底鼓的频率扫描

底鼓最关键的特性是音高下降——从高音快速下降到低音,产生"嘭"的感觉:

// 频率扫描实现
初始频率 ≈ 150Hz
每个时钟:freq = freq - (freq >> 4)  // 指数下降
最终频率 ≈ 50Hz
同时:envelope 快速衰减

这种"频率扫描+包络"是所有808/909风格底鼓的核心。TR-808的底鼓是正弦波+频率扫描的经典案例。

经典鼓机模式

🎵 16步鼓模式库

基本4/4拍 (120BPM):
Kick:  X...X...X...X...
Snare: ....X.......X...
HiHat: X.X.X.X.X.X.X.X.

Disco:
Kick:  X...X...X...X...
Snare: ....X.......X...
HiHat: X.X.XXX.X.X.XXX

Breakbeat:
Kick:  X.....X.X.......
Snare: ....X.......X.X.
HiHat: X.X.X.X.X.X.X.X.

Bossa Nova:
Kick:  X..X....X..X....
Snare: ....X.......X.X.
HiHat: X.X.X.X.X.X.X.X.

NES鼓的实现

NES没有专门的鼓通道!鼓声来自:

NES军鼓的特殊之处:噪声通道只有2种模式(长/短),军鼓通常使用长模式。但通过快速切换模式和音量,可以模拟出不同的打击乐音色。《魂斗罗》的鼓点就大量使用了这种技巧。
  1. 实现鼓机模块,验证4种鼓音色独立工作
  2. 编程基本4/4拍模式,观察16步循环
  3. 调整底鼓频率扫描参数,体会音色变化
  4. 挑战:添加 clap 音色——两个短噪声脉冲间隔10ms

节奏大师 — 实现4音色鼓机,掌握芯片鼓音色的合成方法,理解频率扫描和噪声衰减的原理!

Verilog 实现

drum_machine.v
// drum_machine.v - 鼓机模块
// 4种鼓音色 + 步进序列器
module drum_machine #(
    parameter CLK_FREQ = 50000000,
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32,
    parameter STEPS = 16
)(
    input  wire clk,
    input  wire rst_n,
    input  wire beat_pulse,
    input  wire play,
    // 鼓模式:4个鼓×16步
    input  wire [STEPS-1:0] kick_pattern,
    input  wire [STEPS-1:0] snare_pattern,
    input  wire [STEPS-1:0] hihat_pattern,
    input  wire [STEPS-1:0] openhat_pattern,
    // 输出
    output wire [BIT_DEPTH-1:0] audio_out,
    output wire [3:0] step_indicator
);
    // ─── 步进计数器 ───
    reg [3:0] step;
    reg playing;
    reg [3:0] trigger;  // 4个鼓的触发信号
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            step <= 4'd0;
            playing <= 1'b0;
            trigger <= 4'd0;
        end else begin
            trigger <= 4'd0;
            
            if (play && !playing) begin
                playing <= 1'b1;
                step <= 4'd0;
            end
            
            if (playing && beat_pulse) begin
                // 检查当前步哪些鼓被触发
                trigger[0] <= kick_pattern[step];
                trigger[1] <= snare_pattern[step];
                trigger[2] <= hihat_pattern[step];
                trigger[3] <= openhat_pattern[step];
                
                step <= step + 4'd1;
            end
        end
    end
    
    assign step_indicator = step;
    
    // ─── 底鼓(Kick) ───
    // 短时正弦波 + 快速频率下降(从~150Hz到~50Hz)
    reg [PHASE_BITS-1:0] kick_phase;
    reg [PHASE_BITS-1:0] kick_freq;
    reg [7:0] kick_env;
    reg kick_active;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            kick_phase <= {PHASE_BITS{1'b0}};
            kick_freq <= {PHASE_BITS{1'b0}};
            kick_env <= 8'd0;
            kick_active <= 1'b0;
        end else begin
            if (trigger[0]) begin
                kick_active <= 1'b1;
                kick_freq <= 32'd1288490; // ~150Hz
                kick_env <= 8'd255;
            end else if (kick_active) begin
                kick_phase <= kick_phase + kick_freq;
                // 频率下降
                if (kick_freq > 32'd429497)
                    kick_freq <= kick_freq - (kick_freq >> 4);
                // 包络衰减
                if (kick_env > 8'd4)
                    kick_env <= kick_env - (kick_env >> 3);
                else begin
                    kick_active <= 1'b0;
                    kick_env <= 8'd0;
                end
            end
        end
    end
    
    wire [7:0] kick_out = kick_active ? 
        ((kick_phase[31:24] < 128) ? kick_env : 8'd0) : 8'd0;
    
    // ─── 军鼓(Snare) ───
    // 噪声 + 短三角波 + 快速衰减
    reg [15:0] snare_lfsr;
    reg [7:0] snare_env;
    reg snare_active;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            snare_lfsr <= 16'hBEEF;
            snare_env <= 8'd0;
            snare_active <= 1'b0;
        end else begin
            if (trigger[1]) begin
                snare_active <= 1'b1;
                snare_env <= 8'd255;
            end else if (snare_active) begin
                snare_lfsr <= {snare_lfsr[14:0], snare_lfsr[15] ^ snare_lfsr[13]};
                if (snare_env > 8'd3)
                    snare_env <= snare_env - (snare_env >> 2);
                else begin
                    snare_active <= 1'b0;
                    snare_env <= 8'd0;
                end
            end
        end
    end
    
    wire [7:0] snare_out = snare_active ? 
        ((snare_lfsr[15:8] & 8'h7F) * snare_env) >> 8 : 8'd0;
    
    // ─── 踩镲(Hi-hat) ───
    // 高速噪声 + 极快衰减
    reg [15:0] hh_lfsr;
    reg [7:0] hh_env;
    reg hh_active;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            hh_lfsr <= 16'hCAFE;
            hh_env <= 8'd0;
            hh_active <= 1'b0;
        end else begin
            if (trigger[2]) begin
                hh_active <= 1'b1;
                hh_env <= 8'd200;
            end else if (hh_active) begin
                hh_lfsr <= {hh_lfsr[14:0], hh_lfsr[15] ^ hh_lfsr[12]};
                if (hh_env > 8'd2)
                    hh_env <= hh_env - (hh_env >> 1);
                else begin
                    hh_active <= 1'b0;
                    hh_env <= 8'd0;
                end
            end
        end
    end
    
    wire [7:0] hihat_out = hh_active ? 
        ((hh_lfsr[15:8] & 8'h3F) * hh_env) >> 8 : 8'd0;
    
    // ─── 开镲(Open Hat) ───
    // 与踩镲相同但衰减更慢
    reg [15:0] oh_lfsr;
    reg [7:0] oh_env;
    reg oh_active;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            oh_lfsr <= 16'hDEAD;
            oh_env <= 8'd0;
            oh_active <= 1'b0;
        end else begin
            if (trigger[3]) begin
                oh_active <= 1'b1;
                oh_env <= 8'd180;
            end else if (oh_active) begin
                oh_lfsr <= {oh_lfsr[14:0], oh_lfsr[15] ^ oh_lfsr[11]};
                if (oh_env > 8'd2)
                    oh_env <= oh_env - (oh_env >> 3);
                else begin
                    oh_active <= 1'b0;
                    oh_env <= 8'd0;
                end
            end
        end
    end
    
    wire [7:0] openhat_out = oh_active ? 
        ((oh_lfsr[15:8] & 8'h3F) * oh_env) >> 8 : 8'd0;
    
    // ─── 混音 ───
    assign audio_out = kick_out + snare_out + hihat_out + openhat_out;
endmodule

✅ Verilator验证通过

经典鼓机模式库

以下是芯片音乐中常用的16步鼓模式,可直接用于你的音序器:

🎵 模式库

═══ 基本4/4 ═══
Kick:  X...X...X...X...
Snare: ....X.......X...
HiHat: X.X.X.X.X.X.X.X.

═══ Rock ═══
Kick:  X.......X...X...
Snare: ....X.......X...
HiHat: X.X.X.X.X.X.XXX

═══ Funk ═══
Kick:  X.....X.X....X..
Snare: ....X.......X...
HiHat: X.XXX.X.XXX.X.X.

═══ Hip-Hop ═══
Kick:  X...........X...
Snare: ....X.......X...
HiHat: X.X.X.X.X.X.X.X.

═══ D&B (170BPM) ═══
Kick:  X.....X.........X.
Snare: ....X.......X.X...
HiHat: X.X.X.X.X.X.X.X.X

═══ Breakbeat ═══
Kick:  X.....X.X.......
Snare: ....X.......X.X.
HiHat: X.X.X.X.X.X.X.X.

═══ Bossa Nova ═══
Kick:  X..X....X..X....
Snare: ....X.......X.X.
HiHat: X.X.X.X.X.X.X.X.

═══ Reggae ═══
Kick:  X.......X.......
Snare: ....X.......X...
HiHat: X.X.X.X.X.X.X.X.
OpenH: ..............X.

808 vs 909 鼓音色

特性TR-808TR-909
底鼓正弦波+长衰减,深沉正弦波+噪声点击,紧致
军鼓噪声+低Q滤波,柔软噪声+短正弦,尖锐
踩镲6个方波叠加噪声+带通滤波
风格Hip-hop, R&B, ElectroHouse, Techno, Trance
芯片模拟难度中等(正弦波+包络)较难(需要滤波器)

鼓的力度层次

通过不同的力度值,同一个鼓模式可以产生不同的律动感:

鼓的声学原理

理解真实鼓的声学特性有助于设计更好的合成鼓:

🔊 真实鼓 vs 合成鼓

特征真实底鼓合成底鼓差异
频率50-100Hz + 泛音50-150Hz扫描扫描更戏剧化
衰减指数~500ms指数~300ms合成更短
攻击1-5ms 点击瞬间合成更锐利
泛音丰富非线性简单FM边带真实更复杂

合成鼓不需要完美复制真实鼓——它有自己的美学。808底鼓之所以成为传奇,恰恰是因为它"不像"真实底鼓。

鼓组的力度敏感

通过力度控制鼓的参数,让鼓组更有表现力:

// 力度敏感的底鼓
轻击(vel=64):  起始频率=80Hz,  衰减慢, 音量低
重击(vel=255): 起始频率=150Hz, 衰减快, 音量高

wire [31:0] kick_start_freq = 32'd858993 + (vel << 16);
wire [7:0]  kick_start_env = vel;
wire [7:0]  kick_decay_rate = 3 + (vel >> 5);