全球首款STC32G12K128 多音源芯片音乐合成器 内含ADPCM/FM/Wavetable/PSG多种合成原理
<h1>STC_Chiptune</h1><p>STC32G12K128 多音源芯片合成器。通过 UART 接收命令,PWM 8-bit DAC 输出音频,实时模拟 6 种经典音源芯片 + ADPCM 采样。</p>
<p><strong>开发者</strong>: Denjhang (硬件设计/系统架构), Claude (GLM-5) (固件开发/工具链/上位机)</p>
<p>功能演示视频请看bilibili:STC32G音源演示视频 https://space.bilibili.com/579590644/upload/video</p>
<h2>目录</h2>
<ul>
<li>
<ol>
<li>硬件</li>
</ol>
<ul>
<li>1.1 参数总览</li>
<li>1.2 引脚分配</li>
<li>1.3 PWM DAC 输出原理</li>
</ul>
</li>
<li>
<ol start="2">
<li>音源架构</li>
</ol>
<ul>
<li>2.1 活跃音源总览</li>
<li>2.2 混音与处理</li>
<li>2.3 AY8910 (YM2149)</li>
<li>2.4 SN76489</li>
<li>2.5 FM 合成 (自定义 2-Op)</li>
<li>2.6 Gigatron TTL 波形</li>
<li>2.7 WT 波形表</li>
<li>2.8 ADPCM 采样</li>
<li>2.9 已放弃的音源</li>
</ul>
</li>
<li>
<ol start="3">
<li>UART 协议</li>
</ol>
<ul>
<li>3.1 通用命令</li>
<li>3.2 WT 寄存器</li>
<li>3.3 FM 寄存器</li>
<li>3.4 ADPCM 寄存器</li>
</ul>
</li>
<li>
<ol start="4">
<li>上位机工具</li>
</ol>
<ul>
<li>4.1 VGM 播放器</li>
<li>4.2 ADPCM 鼓声测试</li>
<li>4.3 ADPCM 鼓机 (ini 驱动)</li>
<li>4.4 ADPCM 变频测试</li>
<li>4.5 ADPCM 通道调试</li>
<li>4.6 SF2 旋律测试</li>
<li>4.7 SF2 变频扫频</li>
<li>4.8 WT 扫频测试</li>
<li>4.9 离线工具</li>
<li>4.10 配置文件</li>
</ul>
</li>
<li>
<ol start="5">
<li>工具链与编译</li>
</ol>
<ul>
<li>5.1 工具链</li>
<li>5.2 编译步骤</li>
<li>5.3 编译注意事项</li>
</ul>
</li>
<li>
<ol start="6">
<li>目录结构</li>
</ol>
</li>
<li>
<ol start="7">
<li>开发历程</li>
</ol>
</li>
<li>
<ol start="8">
<li>参考项目与移植说明</li>
</ol>
<ul>
<li>8.1 AY8910 — libvgm</li>
<li>8.2 SN76489 — libvgm</li>
<li>8.3 FM 合成 — ArduinoUnoTinyFmKeyboard</li>
<li>8.4 WT 波形表 — ArduinoUno WaveMemorySyns</li>
<li>8.5 Gigatron — Denjhang_Music_Player</li>
</ul>
</li>
</ul>
<hr />
<h2>1. 硬件</h2>
<h3>1.1 参数总览</h3>
<table>
<thead>
<tr>
<th>参数</th>
<th>值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>MCU</td>
<td>STC32G12K128</td>
<td><strong>32 位 C251 指令集</strong>。128K Flash, 4K SRAM + 8K XRAM</td>
</tr>
<tr>
<td>系统时钟</td>
<td>38 MHz</td>
<td>IRC 内部 RC (Fosc=38000000)</td>
</tr>
<tr>
<td>DAC 输出</td>
<td>P2.0 (PWMA PWM1)</td>
<td>8-bit PWM DAC, 载波 148kHz, 详见下方</td>
</tr>
<tr>
<td>采样率</td>
<td>17640 Hz</td>
<td>Timer0 ISR</td>
</tr>
<tr>
<td>串口</td>
<td>UART1 @ 115200 baud</td>
<td>Timer1, 2048 字节环形缓冲</td>
</tr>
<tr>
<td>LED</td>
<td>P0 端口</td>
<td>8 位, 按活跃音源通道显示</td>
</tr>
<tr>
<td>任务调度</td>
<td>294 tick</td>
<td>~60Hz 调用 process_uart(), 解析命令</td>
</tr>
</tbody>
</table>
<h3>1.2 引脚分配</h3>
<table>
<thead>
<tr>
<th>引脚</th>
<th>功能</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2.0</td>
<td>PWMA PWM1 → 音频输出</td>
<td>8-bit PWM DAC, 接 RC 低通滤波 + 运放 → 扬声器/耳机</td>
</tr>
<tr>
<td>P1.6/P1.7</td>
<td>UART1 TX/RX</td>
<td>115200 baud, 接 USB-TTL</td>
</tr>
<tr>
<td>P0.0-P0.7</td>
<td>LED 指示灯</td>
<td>8-bit, 按音源通道活跃状态显示</td>
</tr>
<tr>
<td>P3.4/P3.5</td>
<td>STC-ISP</td>
<td>编程接口 (TXD2/RXD2)</td>
</tr>
</tbody>
</table>
<h3>1.3 PWM DAC 输出原理</h3>
<h4>硬件配置</h4>
<p>使用 STC32G 内置高级 PWM 模块 (PWMA) 通道 1, 输出到 P2.0:</p>
<pre><code>PWMA_ARR = 255 (8-bit 分辨率, 0-255)
PWMA_CCR1 = sample (ISR 写入, 128=静音中点)
PWMA_PSC = 0 (无分频)
载波频率 = 38MHz / 256 = 148.4kHz
</code></pre>
<p>寄存器配置:</p>
<ul>
<li><code>CCMR1 = 0x68</code> — PWM mode 1, 预装载使能</li>
<li><code>CCER1 = 0x05</code> — CH1 输出使能, 低电平有效</li>
<li><code>PS |= 0x01</code> — P2.0 映射到 PWM1 输出</li>
<li><code>BKR = 0x80</code> — 主输出使能</li>
</ul>
<h4>音频输出链路</h4>
<pre><code>Timer0 ISR (17640Hz)
→ 6 种音源混音 → s16 mix (-128~+127)
→ out = 128 + mix → u8 (0~255)
→ PWMA_CCR1L = out (直接写比较寄存器)
→ P2.0 输出 148kHz PWM 方波
→ RC 低通滤波 (截止 < 10kHz)
→ 运放缓冲
→ 扬声器 / 耳机
</code></pre>
<h4>设计要点</h4>
<ul>
<li>8-bit 分辨率: 256 级, 信噪比 ~48dB, 够用于 chiptune 风格</li>
<li>载波 148kHz: 远高于音频 20kHz, 简单一阶 RC 即可滤除</li>
<li>静音偏置 128: PWM 占空比 50% = 无音频输出</li>
<li><code>OPTIMIZE(8, SPEED)</code>: Keil 最高优化, 确保 ISR 在 56.7us (17640Hz 周期) 内完成</li>
</ul>
<h2>2. 音源架构</h2>
<h3>2.1 活跃音源总览</h3>
<table>
<thead>
<tr>
<th>音源</th>
<th>命令前缀</th>
<th>通道数</th>
<th>渲染频率</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>AY8910</strong> (YM2149)</td>
<td><code>0xA0</code></td>
<td>3 方波 + 噪声 + 包络</td>
<td>17640Hz</td>
<td>完美, ZX Spectrum/MSX 曲目</td>
</tr>
<tr>
<td><strong>SN76489</strong></td>
<td><code>0x50</code></td>
<td>3 方波 + 噪声</td>
<td>17640Hz</td>
<td>Sega Master System, 3 种变体</td>
</tr>
<tr>
<td><strong>FM</strong> (自定义 2-Op)</td>
<td><code>0x51</code></td>
<td>16 voice (32 op)</td>
<td>17640Hz</td>
<td>2-Operator FM, 6 种波形</td>
</tr>
<tr>
<td><strong>Gigatron</strong></td>
<td><code>0xB0</code></td>
<td>4 ch</td>
<td>8820Hz</td>
<td>4ch TTL 波形, 直接写 fnum</td>
</tr>
<tr>
<td><strong>WT</strong> (Wavetable)</td>
<td><code>0xC0</code></td>
<td>4 ch</td>
<td>17640Hz</td>
<td>14 种波形, ADSR 包络</td>
</tr>
<tr>
<td><strong>ADPCM</strong></td>
<td><code>0xC0</code></td>
<td>6 ch</td>
<td>17640Hz</td>
<td>鼓声 + SF2 旋律采样</td>
</tr>
</tbody>
</table>
<h3>2.2 混音与处理</h3>
<p>Timer0 ISR 17640Hz 逐 tick 渲染所有活跃音源, 累加后 clamp 到 8-bit DAC:</p>
<pre><code>mix = ay*1.5 + sn*0.75 + fm*1.5 + gt*1.5 + wt*1.5 + adpcm*1.5
clamp(-128, 127) → 128+offset → PWM
</code></pre>
<h4>UART 处理</h4>
<p>每 294 tick (~60Hz) 调用 <code>process_uart()</code>, 从 2048 字节环形缓冲解析命令。</p>
<h3>2.3 AY8910 (YM2149)</h3>
<p>参考芯片 YM2149 (AY-3-8910), 完美仿真。</p>
<h4>架构</h4>
<ul>
<li><strong>3 方波通道 + 1 噪声通道</strong>: 每通道 12-bit 频率 (reg 0/1, 2/3, 4/5), 4-bit 音量 (reg 8-10)</li>
<li><strong>包络发生器</strong>: 12-bit 频率 (reg 11/12), 4 种形状 (reg 13: hold/alternate/attack/continue)</li>
<li><strong>噪声</strong>: 可编程分频 (reg 6), 17-bit LFSR (<code>seed ^= 0x24000 if LSB, seed >>= 1</code>)</li>
<li><strong>混合</strong>: 每通道独立的音调屏蔽 + 噪声屏蔽 (reg 7)</li>
<li><strong>音量表</strong>: 32 级 (<code>ay_voltbl</code>, 16 级 x2 对称)</li>
</ul>
<h4>频率映射</h4>
<p><code>AY_CLK = 1789772 Hz</code> (NTSC), 24-bit 基数 <code>AY_BASE_INCR = 212779134</code>。 内部计数器每 tick 增加 base_incr, 取高 8 位作为步进量驱动方波/噪声计数器。</p>
<h4>渲染</h4>
<p>每 tick: 方波翻转 → 噪声 LFSR → 包络步进 → 查表混音。 方波输出 = 音量 × 方波/噪声混合, 总输出 = 3 通道之和。</p>
<h3>2.4 SN76489</h3>
<p>参考芯片 SN76489 (Sega Master System), 支持 3 种硬件变体。</p>
<h4>架构</h4>
<ul>
<li><strong>3 方波通道 + 1 噪声通道</strong>: 每通道 10-bit 频率 (高 6 位 + 低 4 位分两次写入), 4-bit 音量</li>
<li><strong>音量表</strong>: 16 级 (<code>sn_voltbl</code>, 对数衰减: 255→0)</li>
<li><strong>噪声</strong>: 可编程分频 (reg 6 低 2 位: /1, /2, /4, /8)</li>
</ul>
<h4>3 种变体</h4>
<table>
<thead>
<tr>
<th>变体</th>
<th>移位寄存器宽度</th>
<th>Taps</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>SN76489 (TI)</td>
<td>15 bit</td>
<td>0x0003 (bit0,1)</td>
<td>原版 TI</td>
</tr>
<tr>
<td>Sega VDP (默认)</td>
<td>16 bit</td>
<td>0x0009 (bit0,3)</td>
<td>SMS/Game Gear</td>
</tr>
<tr>
<td>SN76489A</td>
<td>17 bit</td>
<td>0x000C (bit2,3)</td>
<td>Atari 变体</td>
</tr>
</tbody>
</table>
<p>噪声 LFSR: 白噪声模式 (bit4=0) <code>fb = rng & 1</code>, 周期噪声模式 (bit4=1) <code>fb = (masked != 0) && (masked != taps)</code>。</p>
<h4>渲染</h4>
<p><code>SN_CLK = 3579545 Hz</code>, <code>SN_BASE_INCR = 212779193</code>, 同 AY 的 24-bit 计数器。 方波翻转 + 噪声, 输出 <code>>>2</code> 衰减。</p>
<h4>命令格式</h4>
<p>VGM 标准 2 字节: 首字节 <code>0x80|reg</code>, 次字节数据。锁存机制: <code>dat & 0x80</code> 时更新 last_reg。</p>
<h3>2.5 FM 合成 (自定义 2-Op)</h3>
<p>自定义精简 2-Operator FM 合成核心, 参考 ArduinoUnoTinyFmKeyboard 适配 38MHz/17640Hz。</p>
<h4>架构</h4>
<ul>
<li><strong>16 voice, 32 operator</strong>: 每 voice = op1 (调制器) + op2 (载波器)</li>
<li><strong>6 种波形</strong> x 64 entries = 384 bytes (code 段):</li>
</ul>
<table>
<thead>
<tr>
<th>索引</th>
<th>名称</th>
<th>特征</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>tri</td>
<td>三角波, 64 点</td>
</tr>
<tr>
<td>1</td>
<td>clipsin</td>
<td>削顶正弦, ±20 饱和</td>
</tr>
<tr>
<td>2</td>
<td>rect</td>
<td>矩形波, ±21</td>
</tr>
<tr>
<td>3</td>
<td>sin</td>
<td>标准正弦, ±31</td>
</tr>
<tr>
<td>4</td>
<td>saw</td>
<td>锯齿波, ±31</td>
</tr>
<tr>
<td>5</td>
<td>abssin</td>
<td>绝对值正弦 (全波整流)</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>相位累加器</strong>: 16-bit (8.8 定点), <code>pos += step</code>, <code>idx = (pos >> 8) & 0x3F</code></li>
<li><strong>波形查表</strong>: <code>fm_waves[(wave_idx << 6) | idx]</code> — 位移代替乘法</li>
<li><strong>频率</strong>: MIDI C1-C9 (note 24-127), 104 条目, <code>step = freq * 16384 / 17640</code></li>
</ul>
<h4>2-Operator FM 算法</h4>
<p>每个 voice 每采样周期:</p>
<ol>
<li><strong>OP1 (调制器)</strong>: <code>idx = (pos>>8 + fb_val) & 0x3F</code>, <code>wave = fm_waves</code></li>
<li><strong>OP1 包络</strong>: <code>ch_out = wave * (level+1) * (tl+1) >> 10</code> — 移位代替除法</li>
<li><strong>OP1 反馈</strong>: <code>fb_val = ch_out >> fb</code> (fb=0-7), 累积到下次相位</li>
<li><strong>OP2 (载波器)</strong>: <code>idx = (pos>>8 + op1_ch_out) & 0x3F</code> — 调频核心</li>
<li><strong>OP2 输出</strong>: 同 OP1 包络公式</li>
<li><strong>voice_out = op2_out</strong>, 总输出 = sum(voice_out)</li>
</ol>
<h4>音色模板 (reg 0x00-0x09, 全局共用)</h4>
<table>
<thead>
<tr>
<th>寄存器</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x00</td>
<td>调制器 MULTI (0-15)</td>
</tr>
<tr>
<td>0x01</td>
<td>载波器 MULTI (0-15)</td>
</tr>
<tr>
<td>0x02</td>
<td>调制器 TL (0-31, 调制深度)</td>
</tr>
<tr>
<td>0x03</td>
<td>载波器 TL (高5位) + 反馈 (低3位)</td>
</tr>
<tr>
<td>0x04</td>
<td>调制器 AR</td>
</tr>
<tr>
<td>0x05</td>
<td>载波器 AR</td>
</tr>
<tr>
<td>0x06</td>
<td>调制器 SL</td>
</tr>
<tr>
<td>0x07</td>
<td>载波器 SL</td>
</tr>
<tr>
<td>0x08</td>
<td>调制器波形 (低3位, 0-5)</td>
</tr>
<tr>
<td>0x09</td>
<td>载波器波形 (低3位, 0-5)</td>
</tr>
</tbody>
</table>
<p>Note On 时音色模板自动应用到两个 operator。<code>mul=0</code> 时 step = freq/2。</p>
<h4>ADSR 包络</h4>
<p>4 态: Attack → Decay → Sustain → Release。 速度表 <code>fm_env_cnt</code> (0-255) 控制每级包络的 tick 间隔。 <code>env_cnt</code> 倒计数到 0 触发一级变化。level 范围 0-31。 Round-robin: <code>fm_wait_cnt & 0x0F</code>, 每个 tick 只更新 1 个 operator 的包络 (16 voice = 每 16 tick 轮一圈)。</p>
<h4>运算优化</h4>
<ul>
<li>8.8 定点相位, 位移取波形索引 (>>8, & 0x3F), 无浮点</li>
<li>位移查表 <code>(wave_idx << 6)</code> 代替乘法索引</li>
<li>包络 <code>>> 10</code> 代替 <code>/(31*31)</code> (误差 < 0.5%)</li>
<li>Round-robin 包络 tick 分散 CPU 负载</li>
<li>反馈 <code>>> fb</code> (0-7) 一条移位指令</li>
</ul>
<h3>2.6 Gigatron TTL 波形</h3>
<p>参考 Gigatron TTL 计算机, 4ch 波形合成。</p>
<h4>架构</h4>
<ul>
<li><strong>4 通道</strong>: 共享 256 字节波形表 (可运行时自定义写入)</li>
<li><strong>16-bit 相位累加器</strong>: <code>osc += step</code>, <code>idx = (osc >> 7) & 0xFC</code></li>
<li><strong>fnum 直接写入</strong>: 14-bit (7-bit fnumL + 7-bit fnumH), <code>step = key * 44 / 101</code></li>
<li><strong>波形选择</strong>: <code>wavX</code> (XOR 掩码) + <code>wavA</code> (幅度偏移)</li>
<li><strong>渲染频率</strong>: 8820Hz (每 2 tick 渲染一次)</li>
</ul>
<h4>波形表</h4>
<p>初始化时伪随机生成 4 种波形, 每 4 字节一组: noise/tri/pulse/saw。 运行时可通过 addr 0x14-0xFF 自定义写入任意波形数据。</p>
<h4>渲染</h4>
<p>每通道: <code>idx = (osc >> 7) & 0xFC ^ wavX</code>, <code>val = sound + wavA</code>, clamp 到 0-63。 4 通道求和 + 偏置 3, 输出 <code>samp - 131</code> 映射到 ±128。</p>
<h3>2.7 WT 波形表</h3>
<p>Wavetable 波形表合成器, 参考 fm.c 架构和 ArduinoUno_wavetable_synthesis。</p>
<h4>架构</h4>
<ul>
<li><strong>4 通道</strong>: 独立相位/包络/音量</li>
<li><strong>14 种预置波形</strong> x 128 entries = 1792 bytes (code 段):</li>
</ul>
<table>
<thead>
<tr>
<th>索引</th>
<th>名称</th>
<th>特征</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>sq12</td>
<td>GB duty 12.5% 方波</td>
</tr>
<tr>
<td>1</td>
<td>sq25</td>
<td>GB duty 25% 方波</td>
</tr>
<tr>
<td>2</td>
<td>pulse50</td>
<td>50% 方波</td>
</tr>
<tr>
<td>3</td>
<td>sq75</td>
<td>GB duty 75% 方波</td>
</tr>
<tr>
<td>4</td>
<td>sin</td>
<td>标准正弦, ±31</td>
</tr>
<tr>
<td>5</td>
<td>clipsin</td>
<td>削顶正弦, ±20 饱和</td>
</tr>
<tr>
<td>6</td>
<td>abssin</td>
<td>绝对值正弦 (全波整流)</td>
</tr>
<tr>
<td>7</td>
<td>halfsin</td>
<td>OPL3 Sin1: +sine/0</td>
</tr>
<tr>
<td>8</td>
<td>qsin</td>
<td>OPL3 Sin3: sine/0/sine/0</td>
</tr>
<tr>
<td>9</td>
<td>altsin</td>
<td>OPL3 Sin4: 2x freq +/-/0/0</td>
</tr>
<tr>
<td>10</td>
<td>althalfsin</td>
<td>OPL3 Sin5: 2x freq +/0/0/0</td>
</tr>
<tr>
<td>11</td>
<td>tri</td>
<td>三角波</td>
</tr>
<tr>
<td>12</td>
<td>saw</td>
<td>锯齿波</td>
</tr>
<tr>
<td>13</td>
<td>gb_dmg</td>
<td>DMG 默认 WaveRAM</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>相位累加器</strong>: 16-bit, <code>pos += step</code>, <code>idx = (pos >> 5) & 0x7F</code></li>
<li><strong>波形查表</strong>: <code>wt_waves[(wave_idx << 7) | idx]</code> — 7-bit 位移代替乘法</li>
<li><strong>频率</strong>: MIDI C1-C9 (note 24-127), 104 条目, <code>step = freq * 8192 / 17640</code></li>
<li><strong>波形长度</strong>: 可切换 32/64/128 (reg 0x14)</li>
</ul>
<h4>ADSR 包络</h4>
<p>4 态: Attack → Decay → Sustain → Release。 速度表 <code>wt_env_cnt</code> (同 FM)。 Round-robin: <code>wt_wait_cnt & 0x03</code>, 每 4 tick 轮一圈 (4 通道)。</p>
<h4>渲染</h4>
<p>每通道: 波形查表 → 包络 → <code>ch_out = wave * (level+1) * (vol+1) >> 10</code> → 求和。 音色模板 (reg 0x10-0x13) 全局共用, Note On 时应用到通道。</p>
<h3>2.8 ADPCM 采样</h3>
<p>YM2608 ADPCM Type-A 解码, 49 级 JEDI 查表, 12-bit 累加器。</p>
<h4>编码格式</h4>
<ul>
<li><strong>JEDI 查表</strong>: <code>jedi_table</code> = 784 x s16 (1568 bytes, code 段)</li>
<li><strong>12-bit 累加器</strong>: 每 2 nibble 更新, <code>acc = CLIP12(acc + table)</code></li>
<li><strong>步进表</strong>: <code>adpcm_step_inc = {-16,-16,-16,-16,32,80,112,144}</code></li>
<li><strong>8-bit 压缩比</strong>: ~3.2:1</li>
<li><strong>线性插值</strong>: <code>s_prev + (s_cur - s_prev) * frac >> 8</code></li>
</ul>
<h4>鼓声 (6种, one-shot, 无循环)</h4>
<table>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>采样长 (bytes)</th>
<th>基准 Step</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>BD (Bass Drum)</td>
<td>854</td>
<td>0x0100</td>
<td>底鼓</td>
</tr>
<tr>
<td>1</td>
<td>SD (Snare)</td>
<td>1219</td>
<td>0x0100</td>
<td>军鼓</td>
</tr>
<tr>
<td>2</td>
<td>CY (Cymbal)</td>
<td>5670</td>
<td>0x0080</td>
<td>镲片</td>
</tr>
<tr>
<td>3</td>
<td>HH (Hi-Hat)</td>
<td>732</td>
<td>0x0100</td>
<td>踩镲</td>
</tr>
<tr>
<td>4</td>
<td>TM (Tom)</td>
<td>1219</td>
<td>0x0080</td>
<td>嗵鼓</td>
</tr>
<tr>
<td>5</td>
<td>RS (Rimshot)</td>
<td>244</td>
<td>0x0080</td>
<td>边击</td>
</tr>
</tbody>
</table>
<ul>
<li>变频: Python 端计算 <code>step = base_step * ratio</code>, 通过 0x27/0x2D 写入</li>
<li>鼓变频无上限 (无循环, ISR 无压力)</li>
<li>包络: env_state=0 (无 ADSR), <code>out>>5</code> 增益归一化, <code>total += out * vol >> 5</code></li>
</ul>
<h4>SF2 旋律乐器 (5种, 有循环, DSR 包络)</h4>
<table>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>采样长 (bytes)</th>
<th>循环区间 (nibbles)</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Piano</td>
<td>1164</td>
<td>1915→2127</td>
</tr>
<tr>
<td>1</td>
<td>SlapBass</td>
<td>1835</td>
<td>3017→3512</td>
</tr>
<tr>
<td>2</td>
<td>Guitar</td>
<td>3722</td>
<td>7080→7291</td>
</tr>
<tr>
<td>3</td>
<td>Oboe</td>
<td>2015</td>
<td>2425→2852</td>
</tr>
<tr>
<td>4</td>
<td>Harp</td>
<td>3187</td>
<td>3374→6372</td>
</tr>
</tbody>
</table>
<ul>
<li>ROM: <code>SF2_ROM</code> (11.7 KB), 12-bit 归一化 ADPCM</li>
<li>包络: DSR only (无 Attack), note_on 直接 level=31, note_off 触发 release</li>
<li>变频: 通过 0x27/0x2D 写 step, 上限 0x0400 (有循环, C5 以上死机)</li>
<li>循环: loop 回绕时恢复 acc/adpcm_step 状态 (防漂移)</li>
</ul>
<h4>ADPCM 通道分配</h4>
<p>6 个通道复用: ch0-5 可同时播放鼓声或 SF2 旋律。 通过 data 范围区分:</p>
<ul>
<li>data 0-5: 鼓声</li>
<li>data 16-25: SF2 乐器 (16+inst_idx, inst_idx 0-4)</li>
</ul>
<h4>ADSR 包络 (SF2 旋律 + WT 共用)</h4>
<table>
<thead>
<tr>
<th>参数</th>
<th>地址</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>Attack</td>
<td>0x10 高4位</td>
<td>SF2 旋律不使用 (直接 level=31)</td>
</tr>
<tr>
<td>Decay</td>
<td>0x10 低4位</td>
<td>衰减速度</td>
</tr>
<tr>
<td>Sustain Level</td>
<td>0x11 高4位</td>
<td>0-15</td>
</tr>
<tr>
<td>Sustain</td>
<td>0x11 低4位</td>
<td>持续速度</td>
</tr>
<tr>
<td>Release</td>
<td>0x12</td>
<td>释放速度</td>
</tr>
</tbody>
</table>
<p>包络速度表: <code>pcm_env_cnt = {0,1,2,3,4,5,7,10,13,20,29,43,64,86,128,255}</code> Round-robin: 每 4 tick 轮一圈 (6 通道)。</p>
<h3>2.9 已放弃的音源</h3>
<p>以下 4 种音源在 STC8H8K64U 原型阶段实现, 迁移至 STC32G 后因资源/优先级考虑未启用。源码文件仍保留于 <code>STC32G12K128/</code> 目录, <code>main.c</code> 中 <code>#include</code> 已注释, UART 命令分支保留但无效。</p>
<table>
<thead>
<tr>
<th>音源</th>
<th>命令前缀</th>
<th>通道数</th>
<th>状态</th>
<th>放弃原因</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SCC</strong> (Konami)</td>
<td><code>0xD2</code> (4 字节)</td>
<td>5 ch</td>
<td>源码存在, 注释</td>
<td>WT 完善后不再需要, 且 SCC 需 A51 汇编辅助</td>
</tr>
<tr>
<td><strong>GB DMG</strong> (Game Boy)</td>
<td><code>0xB3</code> (3 字节)</td>
<td>4 ch</td>
<td>源码存在, 注释</td>
<td>功能与 WT sq12/sq25/sq75/gb_dmg 波形重叠</td>
</tr>
<tr>
<td><strong>NES APU</strong> (Ricoh 2A03)</td>
<td><code>0xB4</code> (3 字节)</td>
<td>2 脉冲+1 三角+1 噪声+1 DMC</td>
<td>源码存在, 注释</td>
<td>与 AY8910/SN76489 功能重叠, CPU 开销大</td>
</tr>
<tr>
<td><strong>SAA1099</strong> (Philips)</td>
<td><code>0xBD</code> (3 字节)</td>
<td>6 ch + 2 噪声</td>
<td>源码存在, 注释</td>
<td>通道数需求不足, 与现有音源重叠, 见 SAA 失败记录</td>
</tr>
</tbody>
</table>
<blockquote>
<p>相关文件: <code>scc.c/h</code> <code>gb.c/h</code> <code>nes.c/h</code> <code>saa1099.c/h</code> (及对应的 <code>.LST</code> 编译输出) 相关工具: <code>tools/gen_scc_table.py</code> (SCC 步进查表生成, 历史遗留)</p>
</blockquote>
<hr />
<h2>3. UART 协议</h2>
<h3>3.1 通用命令</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>格式</th>
<th>校验</th>
<th>ACK</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>SN76489</td>
<td><code></code></td>
<td>无</td>
<td>无</td>
<td>2 字节, 透明</td>
</tr>
<tr>
<td>SN76489 变体</td>
<td><code></code></td>
<td>无</td>
<td>无</td>
<td>0=SN76489 1=SegaVDP 2=SN76489A</td>
</tr>
<tr>
<td>AY8910</td>
<td><code></code></td>
<td>无</td>
<td>无</td>
<td>3 字节, 透明</td>
</tr>
<tr>
<td>FM</td>
<td><code></code></td>
<td>XOR</td>
<td>0xAA/0xFF</td>
<td>4 字节</td>
</tr>
<tr>
<td>Gigatron</td>
<td><code></code></td>
<td>XOR</td>
<td>丢弃</td>
<td>4 字节</td>
</tr>
<tr>
<td>WT/ADPCM</td>
<td><code></code></td>
<td>XOR</td>
<td>0xAA/0xFF</td>
<td>4 字节</td>
</tr>
</tbody>
</table>
<h3>3.2 WT 寄存器 (0xC0, addr 0x00-0x14)</h3>
<table>
<thead>
<tr>
<th>地址</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x00-0x03</td>
<td>ch0-3 Note On (data=MIDI note 24-127)</td>
</tr>
<tr>
<td>0x04-0x07</td>
<td>ch0-3 Note Off</td>
</tr>
<tr>
<td>0x08-0x0B</td>
<td>ch0-3 Volume (0-31)</td>
</tr>
<tr>
<td>0x10</td>
<td>ADSR attack</td>
</tr>
<tr>
<td>0x11</td>
<td>ADSR sustain_level</td>
</tr>
<tr>
<td>0x12</td>
<td>ADSR release</td>
</tr>
<tr>
<td>0x13</td>
<td>Wave select (0-13)</td>
</tr>
<tr>
<td>0x14</td>
<td>Wave length (0=32, 1=64, 2=128)</td>
</tr>
</tbody>
</table>
<h3>3.3 FM 寄存器 (0x51, addr 0x00-0x3F)</h3>
<table>
<thead>
<tr>
<th>地址</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x00-0x09</td>
<td>音色参数 (全局共用, 10 字节)</td>
</tr>
<tr>
<td>0x10-0x1F</td>
<td>Note On voice 0-15 (data=MIDI note)</td>
</tr>
<tr>
<td>0x20-0x2F</td>
<td>Note Off voice 0-15</td>
</tr>
<tr>
<td>0x30-0x3F</td>
<td>Volume override voice 0-15 (0-31)</td>
</tr>
</tbody>
</table>
<h3>3.4 ADPCM 寄存器 (0xC0, addr 0x15-0x33)</h3>
<table>
<thead>
<tr>
<th>地址</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x15-0x1A</td>
<td>ch0-5 Note On (data: 0-5=鼓声, 16-25=SF2乐器)</td>
</tr>
<tr>
<td>0x1B-0x20</td>
<td>ch0-5 Note Off</td>
</tr>
<tr>
<td>0x21-0x26</td>
<td>ch0-5 Volume (0-31)</td>
</tr>
<tr>
<td>0x27-0x2C</td>
<td>ch0-5 Step Hi (变频步进高位)</td>
</tr>
<tr>
<td>0x2D-0x32</td>
<td>ch0-5 Step Lo (变频步进低位)</td>
</tr>
<tr>
<td>0x33</td>
<td>MIDI Note (紧跟 SF2 Note On, 24-95)</td>
</tr>
</tbody>
</table>
<hr />
<h2>4. 上位机工具</h2>
<p>所有串口工具默认 COM12 @ 115200 baud (可在脚本头部修改)。 依赖: <code>pip install pyserial</code></p>
<h3>4.1 VGM 播放器</h3>
<pre><code>python tools/vgm_player.py --list --vgm-dir vgm/ay8910 # 列出 AY8910 曲目
python tools/vgm_player.py 10 --vgm-dir vgm/ay8910 # 播放第 10 首
python tools/vgm_player.py --list --vgm-dir vgm/sn76489 # 列出 SN76489 曲目
python tools/vgm_player.py 16 --vgm-dir vgm/sn76489 # 播放第 16 首
python tools/vgm_player.py --list --vgm-dir vgm/opll # 列出 FM 曲目
python tools/vgm_player.py <编号> --vgm-dir vgm/opll # 播放 FM
python tools/vgm_player.py --fm-note 0 60 # FM ch0 C4 单音
python tools/vgm_player.py --fm-scale # FM C1-C9 扫频
python tools/vgm_player.py --wt-scale # WT C2-C6 扫频
python tools/vgm_player.py --loop --vgm-dir vgm/ay8910 # 循环播放
python tools/vgm_player.py --speed 0.5 --vgm-dir vgm/ay8910# 0.5x 变速
</code></pre>
<p>支持芯片: AY8910, SN76489, FM, WT, Gigatron。 参数: <code>--port COM3</code> <code>--baud 115200</code> <code>--dump 1</code> (十六进制转储命令流)</p>
<h3>4.2 ADPCM 鼓声测试</h3>
<pre><code>python tools/adpcm_test.py # 14 种节奏风格全部播放
python tools/adpcm_test.py 3 # 播放第 3 个风格
</code></pre>
<p>硬编码 14 种风格 (Modern/Rock/Pop/Funk/HipHop/Ballad/SlowRock/HardRock/Disco/DancePop/Trance/Jazz/Bossa/Square), 6 种鼓声 (BD/SD/CY/HH/TM/RS)。</p>
<h3>4.3 ADPCM 鼓机 (ini 驱动)</h3>
<pre><code>python tools/adpcm_drumkit_pro.py # 播放全部风格
python tools/adpcm_drumkit_pro.py 3 # 播放第 3 个
python tools/adpcm_drumkit_pro.py list # 列出全部风格
</code></pre>
<p>从 <code>drum.ini</code> 读取 MIDI 映射 (35-81) + 别名 + 音量, 从 <code>drum_patterns/*.ini</code> 读取节奏序列。 支持 per-drum 音量、per-pattern 音量覆盖、swing 节拍。</p>
<h3>4.4 ADPCM 变频测试</h3>
<pre><code>python tools/adpcm_pitch_test.py # 6 鼓 C1→C8 变频扫频
python tools/adpcm_div_test.py # 同鼓不同 step 对比
</code></pre>
<h3>4.5 ADPCM 通道调试</h3>
<pre><code>python tools/adpcm_ch_test.py # 逐通道发声确认
python tools/adpcm_debug.py # 触发通道 + 读 channel mask
python tools/adpcm_led_test.py # 6 通道 LED 确认
</code></pre>
<h3>4.6 SF2 旋律测试</h3>
<pre><code>python tools/sf2_test.py # 5 乐器依次原音播放
python tools/sf2_test.py piano 60 # 钢琴 C4
python tools/sf2_test.py all 60 # 所有乐器 C4
python tools/sf2_test.py chord # 和弦测试
</code></pre>
<h3>4.7 SF2 变频扫频</h3>
<pre><code>python tools/sf2_sweep.py # 所有乐器 C1→4x→C1
python tools/sf2_sweep.py piano # 钢琴
python tools/sf2_pitch_test.py # 鼓路径变频 (无 ADSR)
</code></pre>
<h3>4.8 WT 扫频测试</h3>
<pre><code>python tools/wt_scale_test.py # 14 种波形 C2↔C6 循环扫频
python tools/wt_pcm_debug.py # WT+ADPCM 简单测试
</code></pre>
<h3>4.9 离线工具 (无需串口)</h3>
<p><strong>SF2 采样处理链</strong>:</p>
<pre><code>python tools/sf2_extract.py # SF2 → WAV + JSON + C header
python tools/sf2_preview.py # PC 模拟 MCU ADPCM 解码 (含循环+变频)
python tools/sf2_verify.py # ADPCM 编解码往返验证
python tools/sf2_loop_sim.py # PC 模拟 MCU 循环回绕行为
python tools/sf2_player_sim.py <dir> # 完整 ADPCM 播放模拟
python tools/sf2_drum_test.py # SF2 乐器用鼓编码器验证
python tools/sf2_snese_sim.py # SNES SoundFont 预览 WAV
</code></pre>
<p><strong>ADPCM ROM 生成</strong>:</p>
<pre><code>python tools/gen_adpcm_rom.py # WAV → ADPCM ROM C header (sf2_rom.h)
python tools/adpcm_preprocess.py # YM2608 原始鼓 → 重采样 17640Hz → ADPCM C header
python tools/adpcm_decode_wav.py # ROM → WAV 解码验证
python tools/adpcm_interp_test.py # PC 线性插值算法验证
python tools/adpcm_step_sim.py # PC 定点步进变频模拟
</code></pre>
<p><strong>循环搜索算法</strong>:</p>
<pre><code>python tools/adpcm_perfect_loop.py # 最小方差 + 回文 + 交叉淡出自动寻环
python tools/fix_loop.py # 振幅+斜率匹配搜索更优 loop_end
python tools/polyphone_loop.py # 多点质量评分 + 交叉淡出烘焙
</code></pre>
<p><strong>BRR 格式 (SNES DSP)</strong>:</p>
<pre><code>python tools/brr_test.py # BRR 编解码验证 (GME Spc_Dsp 兼容)
python tools/brr_loop.py # BRR 块状循环模拟
</code></pre>
<p><strong>XI 格式 (YRW801/OPL4 波形)</strong>:</p>
<pre><code>python tools/xi_parse.py --list # 列出 XI 乐器
python tools/xi_parse.py --wav 0x10 0x16 # 导出 WAV
python tools/xi_demo.py # XI 乐器包络+循环预览
python tools/xi_adpcm_test.py # XI → ADPCM 循环测试
python tools/xi_brr_test.py # XI → BRR filter=0 循环测试
python tools/xi_crossfade_test.py # XI 交叉淡出循环测试
python tools/yrw801_extract.py # YRW801 ROM 直接解析 + WAV 导出
</code></pre>
<p><strong>其他</strong>:</p>
<pre><code>python tools/piano_pitch_sweep.py # Grand Piano C3 采样 C1-C8 扫频
python tools/gen_scc_table.py # SCC 步进查表生成 (STC8H 历史遗留)
</code></pre>
<h3>4.10 配置文件</h3>
<table>
<thead>
<tr>
<th>文件</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>tools/drum.ini</code></td>
<td>MIDI GM 35-81 鼓组映射 (drum_id + ratio) + 19 别名 + 全局音量</td>
</tr>
<tr>
<td><code>tools/drum_patterns/*.ini</code></td>
<td>14 个独立风格节奏序列 (bpm/bars/swing/vol)</td>
</tr>
</tbody>
</table>
<h2>5. 工具链与编译</h2>
<h3>5.1 工具链</h3>
<table>
<thead>
<tr>
<th>工具</th>
<th>版本/路径</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>编译器</td>
<td><code>D:/Keil_v5/C251/BIN/C251.exe</code></td>
<td>Keil C251 (非 C51), STC32G 是 C251 架构</td>
</tr>
<tr>
<td>链接器</td>
<td><code>D:/Keil_v5/C251/BIN/L251.exe</code></td>
<td>C251 链接器 (非 BL51)</td>
</tr>
<tr>
<td>HEX 转换</td>
<td><code>D:/Keil_v5/C251/BIN/OH251.exe</code></td>
<td>输出 Intel HEX</td>
</tr>
<tr>
<td>烧录</td>
<td>STC-ISP</td>
<td>手动烧录 (串口 P3.4/P3.5)</td>
</tr>
<tr>
<td>上位机</td>
<td>Python 3 + pyserial</td>
<td>测试/播放工具</td>
</tr>
</tbody>
</table>
<h3>5.2 编译选项</h3>
<pre><code>#pragma LARGE — LARGE 内存模型 (xdata/XRAM)
#pragma OPTIMIZE(8, SPEED) — 最高优化等级, 优速
</code></pre>
<h3>5.3 编译步骤</h3>
<p><strong>方法一: 分步编译</strong></p>
<pre><code class="language-shell">cd STC32G12K128
rm -f *.OBJ
D:/Keil_v5/C251/BIN/C251.exe ay8910.c "LARGE" "OPTIMIZE(8,SPEED)" "NOALIAS" 2>&1
D:/Keil_v5/C251/BIN/C251.exe sn76489.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1
D:/Keil_v5/C251/BIN/C251.exe fm.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1
D:/Keil_v5/C251/BIN/C251.exe gigatron.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1
D:/Keil_v5/C251/BIN/C251.exe wt.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1
D:/Keil_v5/C251/BIN/C251.exe adpcm.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1
D:/Keil_v5/C251/BIN/C251.exe main.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1
D:/Keil_v5/C251/BIN/L251.exe ay8910.OBJ,sn76489.OBJ,fm.OBJ,gigatron.OBJ,wt.OBJ,adpcm.OBJ,main.OBJ TO build/MAIN 2>&1
D:/Keil_v5/C251/BIN/OH251.exe build/MAIN HEXFILE\(build/MAIN.hex\) 2>&1
</code></pre>
<p><strong>方法二: 一行命令 (bash)</strong></p>
<pre><code class="language-shell">cd STC32G12K128 && rm -f *.OBJ ; D:/Keil_v5/C251/BIN/C251.exe ay8910.c "LARGE" "OPTIMIZE(8,SPEED)" "NOALIAS" 2>&1 ; D:/Keil_v5/C251/BIN/C251.exe sn76489.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1 ; D:/Keil_v5/C251/BIN/C251.exe gigatron.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1 ; D:/Keil_v5/C251/BIN/C251.exe fm.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1 ; D:/Keil_v5/C251/BIN/C251.exe wt.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1 ; D:/Keil_v5/C251/BIN/C251.exe adpcm.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1 ; D:/Keil_v5/C251/BIN/C251.exe main.c "LARGE" "OPTIMIZE(8,SPEED)" 2>&1 ; D:/Keil_v5/C251/BIN/L251.exe ay8910.OBJ,sn76489.OBJ,gigatron.OBJ,fm.OBJ,wt.OBJ,adpcm.OBJ,main.OBJ TO build/MAIN 2>&1 ; D:/Keil_v5/C251/BIN/OH251.exe build/MAIN HEXFILE\(build/MAIN.hex\) 2>&1 ; echo "BUILD DONE"
</code></pre>
<h3>5.4 编译注意事项</h3>
<ul>
<li>编译器是 <code>C251.EXE</code> (不是 C51), 链接器是 <code>L251.EXE</code> (不是 BL51)</li>
<li>bash 下用 <code>;</code> 分隔命令, 不能用 <code>&&</code> — C251 WARNING 返回非零会中断 <code>&&</code></li>
<li>bash 下括号要加引号: <code>"LARGE"</code> <code>"OPTIMIZE(8,SPEED)"</code></li>
<li>HEX 参数括号要转义: <code>HEXFILE\(build/MAIN.hex\)</code></li>
<li>必须先 <code>rm -f *.OBJ</code> 清理旧目标文件</li>
<li><code>WARNING C115/C153</code> 可忽略</li>
<li>输出: <code>build/MAIN.hex</code> → 用 STC-ISP 手动烧录</li>
</ul>
<h2>6. 目录结构</h2>
<pre><code>STC_Chiptune/
├── STC32G12K128/ # 固件 (Keil C251)
│ ├── main.c # 主程序 (Timer0 ISR + UART + 混音 + LED)
│ ├── ay8910.c/h # AY8910 仿真 (3ch+噪声+包络)
│ ├── sn76489.c/h # SN76489 仿真 (3ch+噪声, 3变体)
│ ├── fm.c/h # FM 2-Op 合成 (16 voice / 32 op / 6波形)
│ ├── gigatron.c/h # Gigatron TTL 波形 (4ch, 256B共享表)
│ ├── wt.c/h # Wavetable 合成 (4ch / 14波形 / ADSR)
│ ├── adpcm.c/h # ADPCM 解码 (6ch: 鼓声+SF2旋律)
│ ├── types.h # 共享类型定义
│ ├── sf2_rom.h # SF2 采样 ROM (5乐器, 11936B)
│ ├── fmopn_2608rom.h # YM2608 ADPCM 鼓声 ROM + JEDI表
│ └── build/ # 编译输出 (MAIN.hex)
│
├── tools/ # Python 上位机工具
│ ├── vgm_player.py # VGM 播放器 (AY/SN/FM/WT/Gigatron)
│ ├── adpcm_test.py # 鼓声节奏测试 (14风格硬编码)
│ ├── adpcm_drumkit.py # 鼓机基础版
│ ├── adpcm_drumkit_pro.py# 鼓机加强版 (ini驱动)
│ ├── adpcm_pitch_test.py # 鼓变频扫频
│ ├── adpcm_div_test.py # 鼓 step 对比测试
│ ├── adpcm_ch_test.py # ADPCM 逐通道测试
│ ├── adpcm_debug.py # ADPCM 通道 mask 调试
│ ├── adpcm_led_test.py # ADPCM LED 测试
│ ├── gen_adpcm_rom.py # WAV → ADPCM ROM C header 生成
│ ├── adpcm_preprocess.py # YM2608 原始鼓重采样+编码
│ ├── adpcm_decode_wav.py # ROM → WAV 解码验证
│ ├── adpcm_interp_test.py# 线性插值算法验证
│ ├── adpcm_step_sim.py # 定点步进变频模拟
│ ├── adpcm_perfect_loop.py # 自动寻环 (最小方差+回文+交叉淡出)
│ ├── adpcm_wav_test.py # ADPCM 编解码实验
│ ├── fix_loop.py # 搜索更优 loop_end
│ ├── polyphone_loop.py # 多点质量评分寻环
│ ├── brr_test.py # BRR 编解码验证 (SNES DSP)
│ ├── brr_loop.py # BRR 块状循环模拟
│ ├── sf2_extract.py # SF2 → WAV + JSON + C header
│ ├── sf2_test.py # SF2 旋律硬件测试
│ ├── sf2_sweep.py # SF2 变频扫频
│ ├── sf2_pitch_test.py # SF2 鼓路径变频测试
│ ├── sf2_preview.py # SF2 PC 模拟 (ADPCM+循环+变频)
│ ├── sf2_verify.py # ADPCM 编解码往返验证
│ ├── sf2_loop_sim.py # PC 循环回绕模拟
│ ├── sf2_player_sim.py # 完整 ADPCM 播放模拟
│ ├── sf2_drum_test.py # SF2 鼓编码器验证
│ ├── sf2_snese_sim.py # SNES SoundFont 预览
│ ├── xi_parse.py # YRW801 XI 解析+WAV导出
│ ├── xi_demo.py # XI 乐器预览
│ ├── xi_adpcm_test.py # XI → ADPCM 循环测试
│ ├── xi_brr_test.py # XI → BRR filter=0 测试
│ ├── xi_crossfade_test.py# XI 交叉淡出测试
│ ├── xi_brr_crossfade_test.py # XI BRR 交叉淡出测试
│ ├── yrw801_extract.py # YRW801 ROM 解析+WAV导出
│ ├── piano_pitch_sweep.py# Grand Piano C3 扫频
│ ├── wt_scale_test.py # WT 14波形 C2↔C6 扫频
│ ├── wt_pcm_debug.py # WT+ADPCM 简单测试
│ ├── gen_scc_table.py # SCC 查表生成 (历史遗留)
│ ├── drum.ini # MIDI 35-81 鼓组映射+别名+音量
│ └── drum_patterns/ # 14个风格独立节奏 ini
│
├── vgm/ # VGM 曲目库
│ ├── ay8910/ # AY8910 (ZX Spectrum / MSX)
│ ├── sn76489/ # SN76489 (Sega Master System)
│ └── opll/ # FM OPLL (YM2413)
│
├── docs/ # 技术文档
├── README.md # 中文文档
└── README_EN.md # English documentation
</code></pre>
<h2>7. 开发历程</h2>
<h3>7.1 Phase 1-6: STC8H8K64U 时代</h3>
<p>8051 (48MHz) 上的原型开发: SCC/AY8910/SN76489 仿真, GB/NES/SAA 失败尝试。 详见 git history。</p>
<h3>7.2 Phase 7: 迁移 STC32G12K128</h3>
<p>STC32G C251, 38MHz, 128K Flash, 10K SRAM + 8K XRAM。</p>
<ul>
<li>移植全部音源到 C251</li>
<li>新增 FM (自定义 2-Op) 16 voice 合成</li>
<li>新增 Gigatron 4ch TTL 波形</li>
<li>SCC 剔除 (WT 完善, 不再需要)</li>
</ul>
<h3>7.3 Phase 8: ADPCM 采样</h3>
<ul>
<li>实现 YM2608 ADPCM Type-A 解码器 (JEDI 表, 12-bit acc)</li>
<li>从 SF2 音色库提取 5 种乐器 (Piano/SlapBass/Guitar/Oboe/Harp)</li>
<li>6 种鼓声 ROM (BD/SD/CY/HH/TM/RS)</li>
<li>DSR 包络 (无 Attack, 避免 4-bit 量化破音)</li>
<li>鼓声变频: Python 端计算 step, MCU 不做计算</li>
<li>SF2 变频上限 0x0400 (有循环, C5 以上 ISR 死机)</li>
<li>鼓变频无上限 (无循环, 无压力)</li>
</ul>
<h3>7.4 Phase 9: WT 波形扩充</h3>
<ul>
<li>14 种预置波形 (原 6 种 + 8 种 OPL3/GB 风格)</li>
<li>波形长度可切换 (32/64/128)</li>
<li>C2↔C6 循环扫频测试</li>
</ul>
<h3>7.5 Phase 10: 鼓机系统</h3>
<ul>
<li>14 种节奏风格 (Modern/Rock/Pop/Funk/HipHop/Ballad/SlowRock/HardRock/Disco/DancePop/Trance/Jazz/Bossa/Square)</li>
<li>ini 驱动: drum.ini (映射+别名+音量) + drum_patterns/ (独立风格)</li>
<li>MIDI GM Percussion 35-81 全覆盖</li>
<li>19 个变频别名快捷键</li>
</ul>
<h2>8. 参考项目与移植说明</h2>
<p>本项目音源核心均移植自开源项目, 针对 STC32G12K128 (C251, 38MHz, 10K SRAM, 8K XRAM) 做了深度精简和适配。</p>
<h3>8.1 AY8910 — 参考 libvgm (开源, BSD-3-Clause)</h3>
<ul>
<li><strong>原作者</strong>: Couriersud (libvgm), 基于 Ville Hallik / Michael Cuddy / Tatsuyuki Satoh / Fabrice Frances / Nicola Salmoria 的早期工作, 2008 年大幅重写</li>
<li><strong>原始项目</strong>: <code>D:\working\vscode-projects\Reference_Project\vgm_libs\libvgm-master\emu\cores\ay8910.c</code> + <code>ayintf.c</code></li>
<li><strong>原版规模</strong>: ~1620 行 (<code>ay8910.c</code>) + 接口层 + 依赖 <code>EmuStructs.h / snddef.h / EmuCores.h</code> 等框架</li>
</ul>
<p><strong>精简要点 (1620 行 → 230 行, 约 86% 代码量缩减)</strong>:</p>
<ul>
<li>去掉 DEV_DATA/DEV_DEF/DEVFUNC 全部框架接口, 改为直接函数调用 (<code>ay_wr()</code> / <code>ay_render()</code>)</li>
<li>去掉 I/O 端口 (AY8910 的 8-bit parallel port), 只保留纯音频寄存器 (0-15)</li>
<li>去掉通道混合 DAC 模型 (R-C 负载网络、内部阻抗 AY8910_INTERNAL_RESISTANCE=356 等精确模拟), 改为 32 级音量表直查</li>
<li>去掉浮点采样率重采样, 固定 17640Hz 输出, 频率基数预计算 (<code>AY_BASE_INCR</code>)</li>
<li>去掉立体声/声像/静音/选项位等高级功能</li>
<li>去掉 AY8930 扩展模式、YM2149 分频器选择等变体支持</li>
<li>包络保持完整 4 形状 (hold/alternate/attack/continue)</li>
</ul>
<h3>8.2 SN76489 — 参考 libvgm (开源, BSD-3-Clause)</h3>
<ul>
<li><strong>原作者</strong>: Maxim (2001-2002), 从 Delphi 原始实现转换, Charles MacDonald 修改用于 SMS Plus</li>
<li><strong>原始项目</strong>: <code>D:\working\vscode-projects\Reference_Project\vgm_libs\libvgm-master\emu\cores\sn76489.c</code> + <code>sn76489_private.h</code> + <code>sn764intf.c</code></li>
<li><strong>原版规模</strong>: ~530 行 (<code>sn76489.c</code> + <code>sn76489_private.h</code>) + 接口层</li>
</ul>
<p><strong>精简要点 (530 行 → 165 行, 约 69% 代码量缩减)</strong>:</p>
<ul>
<li>去掉 DEV_DATA/DEV_DEF 框架接口, 改为直接函数调用 (<code>sn_wr()</code> / <code>sn_render()</code>)</li>
<li>去掉浮点 <code>dClock</code> 频率跟踪, 改为预计算 <code>SN_BASE_INCR</code> 24-bit 定点基数</li>
<li>去掉方波 "oversampling" 中间位置计算 (IntermediatePos / FLT_MIN), 改为简单翻转</li>
<li>去掉 NeoGeoPocket 双芯片模式 (T6W28 connect)</li>
<li>去掉 GameGear 立体声写入 (GGStereoWrite)</li>
<li>去掉声像 (panning) / 静音 (mute) 接口</li>
<li>保留 3 种变体: SN76489 (TI 15-bit SR) / Sega VDP (16-bit, 默认) / SN76489A (17-bit), 比原版增加了第三种变体</li>
<li>音量表: 原版 16 级 (0-4096, MAME 标准), 精简为 16 级 (0-255, 适配 8-bit 输出)</li>
</ul>
<h3>8.3 FM 合成 — 参考 ArduinoUnoTinyFmKeyboard (开源)</h3>
<ul>
<li><strong>原作者</strong>: Keiji Katahira (ArduinoUnoTinyFmKeyboard, ATmega328P, 20MHz, 24kHz PWM)</li>
<li><strong>原始项目</strong>: <code>D:\working\vscode-projects\Reference_Project\STC-MCU\extracted\ArduinoUnoFMsynsynthesizer-master\ArduinoUnoTinyFmKeyboard\</code></li>
<li><strong>原版规模</strong>: ~668 行 (<code>.ino</code> + <code>fmtone.cpp</code> + <code>fmtone.h</code>), C++ class 架构</li>
<li><strong>原版</strong>: 5 voice (10 op), ATmega328P, 20MHz, 24kHz, AVR Timer1 ISR, 4 种 PWM 引脚可选</li>
</ul>
<p><strong>移植改动 (C++ class → C251 纯 C, 5 voice → 16 voice)</strong>:</p>
<ul>
<li>C++ → 纯 C: <code>FmTone</code> class 拆为全局状态 + 函数 (<code>fm_wr()</code> / <code>fm_render()</code>)</li>
<li>音频: 6 种独立波形表 (<code>wave_sin[]</code> 等) → 合并单一数组 <code>fm_waves</code>, 位移查表 <code>(wave_idx << 6) | idx</code></li>
<li>波形: 6 种 (sin/clipsin/rect/tri/saw/abssin) 完全保留</li>
<li>频率表: PROGMEM <code>tone_freq</code> (MIDI 36-115) → code 段 <code>fm_note_freq</code> (MIDI 24-127, C1-C9)</li>
<li>voice 数: 5 voice → 16 voice, operator 数 10 → 32</li>
<li>包络: <code>envelope_cnt</code> 表完全保留, ADSR 逻辑一致</li>
<li>反馈: <code>fb=7 → fb=8</code> 补正 → 标准 <code>>> fb</code> (0-7), 原版做法过于 hacky</li>
<li>去掉 MIDI 解析 (Note On/Off/Program Change), 改为寄存器直写模式</li>
<li>去掉 poly/mono 模式切换、voice queue、velocity 映射</li>
<li>去掉 SysEx 音色编辑 (<code>sysEx.cpp</code> / <code>deftone.h</code>)</li>
<li>去掉硬件初始化 (AVR Timer PWM), 改为 ISR 回调 <code>fm_render()</code></li>
<li>新增: 全局音色模板机制 (reg 0x00-0x09), Note On 自动 apply tone</li>
</ul>
<h3>8.4 WT 波形表 — 参考 ArduinoUno WaveMemorySyns (开源)</h3>
<ul>
<li><strong>原作者</strong>: Keiji Katahira (ArduinoUno_wavetable_synthesis, WaveMemorySyns)</li>
<li><strong>原始项目</strong>: <code>D:\working\vscode-projects\Reference_Project\STC-MCU\extracted\ArduinoUno_wavetable_synthesis-master\WaveMemorySyns\</code></li>
<li><strong>原版规模</strong>: ~709 行 (<code>.ino</code> + <code>memtone.cpp</code> + <code>envtone.cpp</code> + headers), C++ class 架构</li>
<li><strong>原版</strong>: 4 ch, ATmega328P, 20MHz, 运行时可写波形表 (WaveMemoryEditor)</li>
</ul>
<p><strong>移植改动 (C++ class → C251 纯 C, 可写 RAM → 固化 code 段)</strong>:</p>
<ul>
<li>C++ → 纯 C: <code>MemTone</code> class 拆为全局状态 + 函数 (<code>wt_wr()</code> / <code>wt_render()</code>)</li>
<li>波形: 原版运行时可写 RAM 波形表 → 固化 14 种 128-entry 波形到 code 段 (1792 bytes), 不可运行时修改</li>
<li>波形数量: 原版 1 张可写表 → 14 种预置波形 (sq12/sq25/pulse50/sq75/sin/clipsin/abssin/halfsin/qsin/altsin/althalfsin/tri/saw/gb_dmg)</li>
<li>频率表: PROGMEM → code 段 <code>wt_note_freq</code> (MIDI C1-C9)</li>
<li>包络: <code>envtone.cpp</code> 的 ADSR 独立对象 → 内联 round-robin tick (同 FM 架构)</li>
<li>去掉 MIDI 解析、Echo 效果 (<code>USE_ECHO</code>)</li>
<li>去掉运行时波形编辑 (WaveMemoryEditor / <code>set_value()</code>)</li>
<li>去掉 SysEx 接口 (<code>sysex.cpp</code>)</li>
<li>新增: 波形长度切换 (32/64/128), 音色模板机制</li>
</ul>
<h3>8.5 Gigatron — 移植自 Denjhang_Music_Player (自有项目)</h3>
<ul>
<li><strong>原作者</strong>: Denjhang</li>
<li><strong>原始项目</strong>: <code>D:\working\vscode-projects\YM2163-Midi\Denjhang_Music_Player_v16\src\windows\gigatron\gigatron_emu.c</code> + <code>gigatron_emu.h</code></li>
<li><strong>原版规模</strong>: ~287 行 (<code>.c</code>), C, Windows WinMM 音频回调架构</li>
<li><strong>原版</strong>: 4ch, 44100Hz, 32-bit float 输出, 浮点 RatioCounter 采样率转换, 可选 4/6/8/12/16-bit 音频深度, DC offset 去除, 自定义波形表 + 原始波形表双表, 示波器</li>
</ul>
<p><strong>移植改动 (Windows 音频 → 嵌入式 ISR 回调)</strong>:</p>
<ul>
<li>去掉 RatioCounter 浮点采样率转换 (<code>RC_SET_RATIO</code> / <code>RC_STEP</code>), 改为固定 8820Hz (每 2 tick 渲染一次)</li>
<li>去掉 521 scanline × 59.98Hz Gigatron 帧同步机制</li>
<li>去掉双波形表 (customWaveTable + soundTable), 只保留单一 256 字节波形表</li>
<li>去掉 bit depth 选择 (4/6/8/12/16-bit), 固定 s16 输出</li>
<li>去掉 DC offset 去除 (dc_alpha 滤波)</li>
<li>去掉 volume_scale 浮点缩放</li>
<li>去掉示波器 (scope) 功能</li>
<li>去掉 <code>GigatronState</code> 结构体, 改为 <code>static</code> 全局变量</li>
<li>去掉 <code>srand(time(NULL))</code> 随机种子, 改为固定种子 <code>r = 0x12345678</code> (确定性, 嵌入式无需每次不同)</li>
<li>fnumH: 原版 <code>key *= 4</code> → 移植版 <code>step = key * 44 / 101</code> (适配 17640Hz 采样率)</li>
<li>核心算法完全一致: <code>idx = (osc>>7) & 0xFC ^ wavX</code>, <code>val = sound + wavA</code>, clamp 0-63</li>
</ul>
全部工具+源码打包上传 技术栈强
集成功能多
期待作品效果视频
演示视频已经上传bilibili:
Denjhang的b站视频
STC32G ADPCM Drum Sweep
STC32G ADPCM Piano Sweep Demo
STC32G ADPCM Drum Style Demo 3
STC32G ADPCM Drum Style Demo 2
STC32G ADPCM Drum Style Demo 1
STC32G SN76489 Collision Chaos
denjhang 发表于 2026-6-12 09:44
全部工具+源码打包上传
很好很强大!
声音可以使用HSPWM,144M时钟。
声音使用8位比较粗糙,可以考虑使用11位或12位,会得到很好的音质:
使用8位PWM(时钟38MHz)频率为144KHz,动态48dB。
使用11位HSPWM频率为72KHz,动态66.2dB。
使用12位HSPWM频率为36KHz,动态72.2dB。
作为对比,16位CD的动态为96dB。
音频方面挖掘芯片能力,扩展芯片功能,{:qiang:}{:qiang:}{:qiang:}。
清晰、简洁、严谨的技术工作者的风格,{:qiang:}{:qiang:}{:qiang:}。 梁工 发表于 2026-6-12 11:46
很好很强大!
声音可以使用HSPWM,144M时钟。
声音使用8位比较粗糙,可以考虑使用11位或12位,会得到很好 ...
8-bit PWM DAC 在 38MHz 下频率 = 38M/256 ≈ 148kHz,远超人耳上限(20kHz),无超声波底噪。
10-bit 需要 ARR=1024,同样 38MHz 下频率降到 37kHz,进入可听范围边缘,产生超声波底噪。要 10-bit 无底噪需要 PWM 频率 ≥ 140kHz,即时钟 ≥ 144MHz(PLL),但 STC32G12K128 的 38MHz IRC 不是 PLL 标准输入频率,锁频不稳定。
所以 8-bit + 38MHz 是当前硬件的最优解,分辨率和底噪的平衡点。要突破必须换 STC32G144K246 的硬件 DAC。
页:
[1]