第24课:完整音序器

阶段5:实战项目

本课整合所有模块,构建4通道完整音序器——方波主旋律、方波和声、三角波低音和噪声鼓组。这是NES音乐的完整通道复刻,加上模式排列功能。

完整系统架构

📐 4通道音序器架构

┌────────────────────────────────────────────┐
│              歌曲控制层                      │
│  歌曲顺序表 → 模式选择 → 步进控制           │
└────────────────┬───────────────────────────┘
                 │ beat_pulse + step
     ┌───────────┼───────────┬──────────────┐
     ▼           ▼           ▼              ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐
│ CH1:方波│ │ CH2:方波│ │ CH3:三角│ │ CH4:噪声 │
│ 主旋律  │ │ 和声    │ │ 低音    │ │ 鼓组     │
│ +ADSR   │ │ +ADSR   │ │ +ADSR   │ │ Kick/Sn  │
│ 占空比  │ │ 25%DC   │ │ 固定音量│ │ /HH/OH   │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬─────┘
     └──────┬────┴──────┬────┴──────────┘
            ▼           ▼
     ┌──────────────────────────┐
     │     4通道混音器          │
     │     + 主音量控制         │
     └──────────┬───────────────┘
                ▼
            audio_out

模式排列实战

用8个模式编一首简单的歌:

模式0: Intro (空旋律, 只有踩镲)
模式1: Verse A (简单旋律 + 基本鼓)
模式2: Verse B (旋律变奏 + 鼓加花)
模式3: Chorus (全通道满编)
模式4: Bridge (低音+鼓, 旋律休止)

歌曲顺序: [0, 1, 1, 3, 1, 2, 3, 4, 3, 3]
           Intro → VerseA ×2 → Chorus → 
           VerseA → VerseB → Chorus → 
           Bridge → Chorus ×2

实时参数控制

完整的音序器需要支持实时参数修改:

  1. 实现4通道完整音序器
  2. 编写8个模式的完整音乐数据
  3. 测试:从Intro到Chorus的模式切换
  4. 挑战:添加效果命令系统——在乐谱中嵌入颤音、滑音、音量变化指令

音序器架构师 — 实现4通道完整音序器,掌握模式排列和通道协调,理解芯片音乐系统的完整架构!

Verilog 实现

full_sequencer.v
// full_sequencer.v - 完整音序器
// 4通道完整音序系统:旋律+和声+低音+鼓
module full_sequencer #(
    parameter CLK_FREQ = 50000000,
    parameter BIT_DEPTH = 8,
    parameter PHASE_BITS = 32,
    parameter PATTERN_LEN = 16,
    parameter NUM_PATTERNS = 8
)(
    input  wire clk,
    input  wire rst_n,
    input  wire play,
    input  wire stop,
    input  wire [9:0] bpm,
    input  wire [3:0] song_order [0:31],
    input  wire [4:0] song_length,
    // 旋律通道模式数据
    input  wire [6:0] mel1_notes [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
    input  wire [7:0] mel1_vels  [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
    // 和声通道
    input  wire [6:0] mel2_notes [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
    input  wire [7:0] mel2_vels  [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
    // 低音通道
    input  wire [6:0] bass_notes [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
    input  wire [7:0] bass_vels  [0:NUM_PATTERNS-1][0:PATTERN_LEN-1],
    // 鼓模式
    input  wire [15:0] kick_patterns [0:NUM_PATTERNS-1],
    input  wire [15:0] snare_patterns [0:NUM_PATTERNS-1],
    input  wire [15:0] hihat_patterns [0:NUM_PATTERNS-1],
    // 主音量
    input  wire [BIT_DEPTH-1:0] master_vol,
    // 输出
    output wire [BIT_DEPTH-1:0] audio_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'd12500000; // 240BPM十六分音符
            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 [3:0] step;
    reg [4:0] song_pos;
    reg [3:0] active_pattern;
    reg playing;
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            step <= 4'd0; song_pos <= 5'd0;
            active_pattern <= 4'd0; playing <= 1'b0;
        end else begin
            if (play && !playing) begin
                playing <= 1'b1; step <= 4'd0;
                song_pos <= 5'd0;
                active_pattern <= song_order[0];
            end else if (stop) begin
                playing <= 1'b0;
            end else if (playing && beat_pulse) begin
                if (step >= PATTERN_LEN - 1) begin
                    step <= 4'd0;
                    song_pos <= (song_pos >= song_length - 1) ? 5'd0 : song_pos + 5'd1;
                    active_pattern <= song_order[song_pos];
                end else
                    step <= step + 4'd1;
            end
        end
    end
    
    // ─── 频率查找 (简化) ───
    function [31:0] note_to_freq;
        input [6:0] note;
        case (note)
            7'd36: note_to_freq = 32'd2810;   // C2
            7'd48: note_to_freq = 32'd5612;   // C3
            7'd55: note_to_freq = 32'd8393;   // G3
            7'd60: note_to_freq = 32'd11284;  // C4
            7'd64: note_to_freq = 32'd14197;  // E4
            7'd67: note_to_freq = 32'd16870;  // G4
            7'd69: note_to_freq = 32'd18928;  // A4
            7'd72: note_to_freq = 32'd22491;  // C5
            default: note_to_freq = 32'd0;
        endcase
    endfunction
    
    // ─── 旋律1 (方波) ───
    reg [31:0] mel1_phase;
    wire [6:0] mel1_note = mel1_notes[active_pattern][step];
    wire [7:0] mel1_vel = mel1_vels[active_pattern][step];
    
    always @(posedge clk)
        mel1_phase <= mel1_phase + note_to_freq(mel1_note);
    
    wire [7:0] mel1_wave = (mel1_note > 0) ? (mel1_phase[31] ? 8'd220 : 8'd0) : 8'd0;
    
    // ─── 旋律2 (方波25%) ───
    reg [31:0] mel2_phase;
    wire [6:0] mel2_note = mel2_notes[active_pattern][step];
    wire [7:0] mel2_vel = mel2_vels[active_pattern][step];
    
    always @(posedge clk)
        mel2_phase <= mel2_phase + note_to_freq(mel2_note);
    
    wire [7:0] mel2_wave = (mel2_note > 0) ? 
        ((mel2_phase[31:30] == 2'b00) ? 8'd180 : 8'd0) : 8'd0;
    
    // ─── 低音 (三角波) ───
    reg [31:0] bass_phase;
    wire [6:0] bass_note = bass_notes[active_pattern][step];
    wire [7:0] bass_vel = bass_vels[active_pattern][step];
    
    always @(posedge clk)
        bass_phase <= bass_phase + note_to_freq(bass_note);
    
    wire [7:0] bass_wave = (bass_note > 0) ?
        (bass_phase[31] ? ~bass_phase[30:23] : bass_phase[30:23]) : 8'd0;
    
    // ─── 鼓通道 ───
    reg [15:0] noise_lfsr;
    reg [7:0] kick_env, snare_env, hh_env;
    wire [15:0] cur_kick = kick_patterns[active_pattern];
    wire [15:0] cur_snare = snare_patterns[active_pattern];
    wire [15:0] cur_hh = hihat_patterns[active_pattern];
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            noise_lfsr <= 16'hCAFE;
            kick_env <= 8'd0; snare_env <= 8'd0; hh_env <= 8'd0;
        end else begin
            noise_lfsr <= {noise_lfsr[14:0], noise_lfsr[15] ^ noise_lfsr[13]};
            
            if (beat_pulse && cur_kick[step]) kick_env <= 8'd200;
            else if (kick_env > 2) kick_env <= kick_env - (kick_env >> 3);
            
            if (beat_pulse && cur_snare[step]) snare_env <= 8'd180;
            else if (snare_env > 2) snare_env <= snare_env - (snare_env >> 2);
            
            if (beat_pulse && cur_hh[step]) hh_env <= 8'd140;
            else if (hh_env > 2) hh_env <= hh_env - (hh_env >> 1);
        end
    end
    
    wire [7:0] drum_out = (kick_env >> 1) + 
                          (({noise_lfsr[15:8]} & 8'h7F) * snare_env >> 8) +
                          (({noise_lfsr[14:7]} & 8'h3F) * hh_env >> 8);
    
    // ─── 混音 ───
    wire [15:0] mel1_out = mel1_wave * mel1_vel;
    wire [15:0] mel2_out = mel2_wave * mel2_vel;
    wire [15:0] bass_out = bass_wave * bass_vel;
    
    wire signed [17:0] mix = $signed({1'b0, mel1_out[15:8]}) + 
                             $signed({1'b0, mel2_out[15:8]}) +
                             $signed({1'b0, bass_out[15:8]}) +
                             $signed({1'b0, drum_out});
    
    wire [15:0] master_out = mix[7:0] * master_vol;
    assign audio_out = master_out[15:8];
endmodule

✅ Verilator验证通过

效果命令系统设计

完整的音序器需要在乐谱中嵌入效果命令:

🔧 效果命令编码方案

// 每个步的数据结构扩展
// [7:0] note_pitch   - MIDI音符(0=休止)
// [7:0] note_vel     - 力度
// [3:0] note_dur     - 时值
// [7:0] effect_cmd   - 效果命令类型
// [7:0] effect_param - 效果参数

// 效果命令定义
EFX_NONE      = 8'h00  // 无效果
EFX_VIBRATO   = 8'h01  // 颤音(参数=速度+深度)
EFX_PORTAMENTO= 8'h02  // 滑音(参数=速率)
EFX_VOL_SLIDE = 8'h03  // 音量滑动(参数=方向+速率)
EFX_ARPEGGIO  = 8'h04  // 琶音(参数=两个偏移半音)
EFX_DUTY_CYC  = 8'h05  // 占空比切换(参数=新占空比)
EFX_PITCH_BEND= 8'h06  // 弯音(参数=偏移量)
EFX_TEMPO     = 8'h07  // 速度变化(参数=新BPM)
EFX_JUMP      = 8'h08  // 跳转(参数=目标模式)
EFX_RET_TRIG  = 8'h09  // 快速重触发(参数=间隔)

4通道编曲模板

4通道的标准编曲模板:

// CH1 (方波50%): 主旋律 - 负责核心旋律线
// CH2 (方波25%): 和声/副旋律 - 伴奏或对位
// CH3 (三角波):  低音线 - 和声根音+节奏
// CH4 (噪声):    鼓组 - Kick+Snare+HiHat

// 标准分配:
// CH1 = 40%听感权重
// CH2 = 25%听感权重  
// CH3 = 20%听感权重
// CH4 = 15%听感权重

编曲中的"减法"思维

4通道看似够用,但要同时表现旋律+和声+低音+鼓,每个通道都需要做不止一件事

专业Tracker软件的功能

我们的4通道音序器实现了Tracker的核心功能,但专业Tracker还有更多:

🔧 专业Tracker功能清单

功能我们的实现专业Tracker
通道数44-32
效果命令基础20+种
自动化参数自动化
采样支持DPCM/PCM
MIDI输出完整MIDI
波形显示实时可视化
撤销/重做完整历史

歌曲数据的ROM优化

4通道×多个模式×16步的数据量可能很大。优化方法:

// 1. 使用变长编码
// 休止符和重复音符用短编码
// 只有新音符需要完整数据

// 2. 模式共享
// 相同的鼓模式被多个段落引用
// 不需要存储多份

// 3. 差分编码
// 存储相邻步的差值而非绝对值
// 旋律通常移动很小,差分编码很高效

// 4. 压缩
// 使用简单的RLE或Huffman压缩
// 解压在播放时实时进行