第23课:鼓机+旋律

阶段5:实战项目

真正的芯片音乐需要旋律+节奏同时进行。本课将旋律通道和鼓机通道组合为双通道系统,实现旋律和鼓点的同步播放——这是从"演示"到"音乐"的关键跨越。

双通道同步

两个通道必须共享同一个节拍时钟,否则会出现节奏混乱:

📐 双通道架构

        ┌──────────────┐
        │  节拍时钟    │
        │  (BPM=120)   │
        └──────┬───────┘
               │ beat_pulse
        ┌──────┴───────┐
        ▼              ▼
  ┌──────────┐  ┌──────────┐
  │ 旋律通道  │  │  鼓通道   │
  │ 方波/三角 │  │ Kick/Sn  │
  │ +序列器   │  │ /HH+16步 │
  └─────┬────┘  └─────┬────┘
        │             │
        └──────┬──────┘
               ▼
        ┌──────────┐
        │  混音器   │
        └─────┬────┘
              ▼
          audio_out

NES的四通道架构

NES的典型通道分配:

通道波形典型角色占比
CH1方波主旋律40%
CH2方波和声/副旋律25%
CH3三角波低音线20%
CH4噪声鼓/打击乐15%

4个通道要完成旋律+和声+低音+鼓的全部工作——这种限制催生了极富创意的编曲技巧。

编曲技巧:4通道编曲法

"快速琶音"是芯片音乐最标志性的技巧。在《洛克人2》的Wily Stage 1中,方波通道以约50Hz的速度在3个音符间交替,产生了"和弦"的错觉。这在技术上只是快速的单音序列,但人耳将其感知为和弦。
  1. 实现鼓+旋律双通道系统
  2. 编程一段8小节的旋律+4/4拍鼓模式
  3. 实验:调高旋律音量 vs 调高鼓音量,找到平衡点
  4. 挑战:添加三角波低音通道,实现3通道编曲

编曲初学者 — 实现旋律+鼓的双通道同步系统,掌握芯片音乐编曲的基本技巧!

Verilog 实现

drums_melody.v
// drums_melody.v - 鼓机+旋律组合系统
// 2通道:旋律通道 + 鼓组通道
module drums_melody #(
    parameter CLK_FREQ = 50000000,
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32
)(
    input  wire clk,
    input  wire rst_n,
    input  wire play,
    input  wire stop,
    // 旋律参数
    input  wire [1:0] melody_wave,
    input  wire [9:0] bpm,
    // 旋律数据
    input  wire [6:0] mel_note [0:15],
    input  wire [7:0] mel_vel  [0:15],
    input  wire [3:0] mel_dur  [0:15],
    input  wire [6:0] mel_length,
    // 鼓模式
    input  wire [15:0] kick_pattern,
    input  wire [15:0] snare_pattern,
    input  wire [15:0] hihat_pattern,
    // 输出
    output wire [BIT_DEPTH-1:0] audio_out,
    output wire [3:0] step_out
);
    // ─── 节拍时钟 ───
    reg [31:0] beat_counter;
    reg [31:0] beat_period;
    reg beat_pulse;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            beat_counter <= 32'd0;
            beat_period <= 32'd25000000;
            beat_pulse <= 1'b0;
        end else begin
            beat_pulse <= 1'b0;
            if (beat_counter >= beat_period - 1) begin
                beat_counter <= 32'd0;
                beat_pulse <= 1'b1;
            end else
                beat_counter <= beat_counter + 32'd1;
        end
    end
    
    // ─── 旋律通道 ───
    reg [6:0] mel_step;
    reg [3:0] mel_dur_cnt;
    reg mel_playing;
    reg mel_trigger;
    reg [6:0] mel_current_note;
    reg [7:0] mel_current_vel;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            mel_step <= 7'd0; mel_dur_cnt <= 4'd0;
            mel_playing <= 1'b0; mel_trigger <= 1'b0;
            mel_current_note <= 7'd0; mel_current_vel <= 8'd0;
        end else begin
            mel_trigger <= 1'b0;
            if (play && !mel_playing) begin
                mel_playing <= 1'b1; mel_step <= 7'd0;
                mel_dur_cnt <= 4'd0; mel_trigger <= 1'b1;
                mel_current_note <= mel_note[0];
                mel_current_vel <= mel_vel[0];
            end else if (stop) begin
                mel_playing <= 1'b0;
            end else if (mel_playing && beat_pulse) begin
                if (mel_dur_cnt >= mel_dur[mel_step] - 1) begin
                    mel_dur_cnt <= 4'd0;
                    if (mel_step >= mel_length - 1) mel_step <= 7'd0;
                    else mel_step <= mel_step + 7'd1;
                    mel_trigger <= 1'b1;
                    mel_current_note <= mel_note[mel_step];
                    mel_current_vel <= mel_vel[mel_step];
                end else
                    mel_dur_cnt <= mel_dur_cnt + 4'd1;
            end
        end
    end
    
    // 旋律频率查找
    reg [PHASE_BITS-1:0] mel_freq;
    always @(*) begin
        case (mel_current_note)
            7'd60: mel_freq = 32'd11284;
            7'd62: mel_freq = 32'd12657;
            7'd64: mel_freq = 32'd14197;
            7'd65: mel_freq = 32'd15037;
            7'd67: mel_freq = 32'd16870;
            7'd69: mel_freq = 32'd18928;
            7'd72: mel_freq = 32'd22491;
            default: mel_freq = 32'd11284;
        endcase
    end
    
    // 旋律波形
    reg [PHASE_BITS-1:0] mel_phase;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) mel_phase <= 32'd0;
        else mel_phase <= mel_phase + mel_freq;
    end
    
    reg [7:0] mel_wave;
    always @(*) begin
        case (mel_wave)
            2'b00: mel_wave = mel_phase[31] ? 8'd255 : 8'd0;
            2'b01: mel_wave = mel_phase[31] ? ~mel_phase[30:23] : mel_phase[30:23];
            2'b10: mel_wave = mel_phase[31:24];
            default: mel_wave = 8'd0;
        endcase
    end
    
    reg [7:0] mel_env;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) mel_env <= 8'd0;
        else if (mel_trigger) mel_env <= mel_current_vel;
        else if (mel_env > 2) mel_env <= mel_env - 1;
    end
    
    wire [7:0] mel_out = (mel_wave * mel_env) >> 8;
    
    // ─── 鼓通道 ───
    reg [3:0] drum_step;
    reg drum_playing;
    reg [3:0] drum_trig;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            drum_step <= 4'd0; drum_playing <= 1'b0; drum_trig <= 4'd0;
        end else begin
            drum_trig <= 4'd0;
            if (play && !drum_playing) drum_playing <= 1'b1;
            else if (stop) drum_playing <= 1'b0;
            else if (drum_playing && beat_pulse) begin
                drum_trig[0] <= kick_pattern[drum_step];
                drum_trig[1] <= snare_pattern[drum_step];
                drum_trig[2] <= hihat_pattern[drum_step];
                drum_step <= drum_step + 4'd1;
            end
        end
    end
    
    // 简化鼓音色
    reg [15:0] kick_lfsr, snare_lfsr, hh_lfsr;
    reg [7:0] kick_env, snare_env, hh_env;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            kick_lfsr <= 16'h1234; snare_lfsr <= 16'h5678; hh_lfsr <= 16'h9ABC;
            kick_env <= 8'd0; snare_env <= 8'd0; hh_env <= 8'd0;
        end else begin
            if (drum_trig[0]) kick_env <= 8'd220;
            else if (kick_env > 2) kick_env <= kick_env - (kick_env >> 3);
            
            if (drum_trig[1]) snare_env <= 8'd200;
            else if (snare_env > 2) begin
                snare_lfsr <= {snare_lfsr[14:0], snare_lfsr[15] ^ snare_lfsr[13]};
                snare_env <= snare_env - (snare_env >> 2);
            end
            
            if (drum_trig[2]) hh_env <= 8'd160;
            else if (hh_env > 2) begin
                hh_lfsr <= {hh_lfsr[14:0], hh_lfsr[15] ^ hh_lfsr[12]};
                hh_env <= hh_env - (hh_env >> 1);
            end
        end
    end
    
    wire [7:0] drum_out = (kick_env >> 1) + 
                          ((snare_lfsr[15:8] * snare_env) >> 8) +
                          ((hh_lfsr[15:8] * hh_env) >> 8);
    
    // ─── 混音 ───
    assign audio_out = mel_out + drum_out;
    assign step_out = {drum_step, 1'b0};
endmodule

✅ Verilator验证通过

双通道编曲实战

让我们用2通道系统编一段完整的芯片音乐:

🎵 8小节编曲示例

BPM=120, 4/4拍

小节1-4: 主歌(Verse)
  旋律: C4 E4 G4 C5 | B4 G4 E4 C4 | A3 C4 E4 A4 | G4 E4 C4 G3
  鼓:   K-S-K-S- | K-S-K-S- | K-S-K-S- | K-S-K-SK
  低音: C3------- | C3------- | A2------- | G2-------

小节5-8: 副歌(Chorus)  
  旋律: C5 B4 A4 G4 | F4 E4 D4 C4 | C5.B4 A4.G4 | F4.E4 D4.C4
  鼓:   K-S-K-S-K | KS-K-S-K- | K-S-KSS- | K-S-K---
  低音: C3-G2---- | F2-C3---- | A2-G2---- | F2-C3---

旋律和鼓的时间关系

旋律和鼓虽然共享节拍时钟,但可以有不同的时间分辨率:

混音平衡的艺术

2通道系统中旋律和鼓的音量平衡至关重要:

风格旋律:鼓比例听感
旋律为主3:1旋律突出,鼓是辅助
平衡2:1两者平等
节奏为主1:1鼓和旋律并重
鼓主导1:2节奏感强,旋律是点缀

芯片音乐的流派

芯片音乐已经发展出多种风格流派:

🎵 芯片音乐流派

流派平台BPM特点
NES风格Famicom120-160快速琶音, 4通道编曲
Game Boy风格DMG130-170方波+波表, 粗犷
C64 SID风格C64100-140滤波器, 锯齿波
Mega Drive FMGenesis120-150FM合成, 金属感
Acid芯片LSDj130-145303风格滑音+共振
Ambient芯片任意60-90慢速, 氛围感

3通道编曲的高级技巧

用3通道(旋律+低音+鼓)能做出多少?比你想的多: