本课整合所有模块,构建4通道完整音序器——方波主旋律、方波和声、三角波低音和噪声鼓组。这是NES音乐的完整通道复刻,加上模式排列功能。
┌────────────────────────────────────────────┐
│ 歌曲控制层 │
│ 歌曲顺序表 → 模式选择 → 步进控制 │
└────────────────┬───────────────────────────┘
│ 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
完整的音序器需要支持实时参数修改:
音序器架构师 — 实现4通道完整音序器,掌握模式排列和通道协调,理解芯片音乐系统的完整架构!
// 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通道的标准编曲模板:
// CH1 (方波50%): 主旋律 - 负责核心旋律线
// CH2 (方波25%): 和声/副旋律 - 伴奏或对位
// CH3 (三角波): 低音线 - 和声根音+节奏
// CH4 (噪声): 鼓组 - Kick+Snare+HiHat
// 标准分配:
// CH1 = 40%听感权重
// CH2 = 25%听感权重
// CH3 = 20%听感权重
// CH4 = 15%听感权重
4通道看似够用,但要同时表现旋律+和声+低音+鼓,每个通道都需要做不止一件事:
我们的4通道音序器实现了Tracker的核心功能,但专业Tracker还有更多:
| 功能 | 我们的实现 | 专业Tracker |
|---|---|---|
| 通道数 | 4 | 4-32 |
| 效果命令 | 基础 | 20+种 |
| 自动化 | 无 | 参数自动化 |
| 采样支持 | 无 | DPCM/PCM |
| MIDI输出 | 无 | 完整MIDI |
| 波形显示 | 无 | 实时可视化 |
| 撤销/重做 | 无 | 完整历史 |
4通道×多个模式×16步的数据量可能很大。优化方法:
// 1. 使用变长编码
// 休止符和重复音符用短编码
// 只有新音符需要完整数据
// 2. 模式共享
// 相同的鼓模式被多个段落引用
// 不需要存储多份
// 3. 差分编码
// 存储相邻步的差值而非绝对值
// 旋律通常移动很小,差分编码很高效
// 4. 压缩
// 使用简单的RLE或Huffman压缩
// 解压在播放时实时进行