DOOM 作为经典 DOS 游戏,其音频系统高度优化,但 PC 扬声器(PC Speaker)驱动仅限于音效(SFX),未充分利用定时器中断潜力实现音乐播放。早期担忧实时合成占用过多 CPU 资源干扰游戏逻辑,尤其在 286 上成立,但 486 等处理器足以支撑。逆向工程证明,通过 PIT(8253/8254 可编程间隔定时器)通道 2 生成方波、通道混合,并在中断中处理,可无缝添加音乐而不影响 35Hz 游戏 tick。
PC Speaker 发声依赖 PIT 通道 2(端口 0x42 数据、0x43 控制、0x61 门控)。基础原理:PIT 输入时钟 1.193182 MHz,工作模式 3(方波发生器)下,输出频率 f = 1193180 / N,其中 N 为 16 位除数(低字节先写入)。例如,896 Hz 对应 N=1333(0x0533)。要发声,先读 0x61 保存 bits,出 0x61 (bits | 3) 启用门控;播放后出 (bits & 0xFC) 关闭。DOOM 原生 SFX 使用此机制,但音乐需连续波形合成,故引入中断驱动。
关键挑战:DOOM 主循环以 35Hz tick(PIT ch0 IRQ0,每 55.28ms)驱动游戏逻辑,不能阻塞。解决方案:利用 PIT ch2 中断(或自定义高频 tick)生成采样波形,在 IRQ 中混合多通道音频。参考逆向补丁,使用 pcsp 格式高效编码音乐:每个 32-bit tone cell 含 16-bit 频率(Hz)、4-bit 时长缩放(秒 * 10^-scale)、12-bit 时长值。解码只需整数运算,转为 PIT 调用。
多通道混合实现优先级 mixer,借鉴 DOOM Adlib 驱动基础。架构:
- 采样率与缓冲:目标 7-14 kHz 采样(匹配 PC Speaker 带宽),每 tick 生成 2000-4000 样本。使用环形缓冲(4KB),中断填充。
- 波形生成:每个通道独立 PIT ch2 模拟,或软件 DDS(Direct Digital Synthesis):phase_acc += freq_step,输出 sin (phase) 近似方波(PC Speaker 仅单声道方波)。
- 混合:浮点无效,DOS 下整数加法:sample = ch1_vol * wave1 + ch2_vol * wave2 + .../max_ch;限幅 [-127,127] 转 0/1。
- 中断集成:挂钩 PIT ch0(35Hz)子中断,或独立 ch2 IRQ(~10kHz)。优先级:SFX > 音乐 > 环境。帖子验证:“运行游戏前后无明显速度差异”。
可落地参数与清单:
- 频率表(常见 DOOM MIDI 转 PCSP):C4=262Hz (N=4558), E4=330Hz (N=3619), G4=392Hz (N=3045)。全谱预计算:N [f] = 1193180 /f,f 步进 1Hz。
- 时长编码:scale=0 (0.1s 单位),duration=1000 → 100s;实际 DOOM 曲目~120s,压缩至 1KB。
- 混合阈值:通道数≤4,vol=0-15,mix_gain=16。过载阈值:>15 静音 1 tick。
- 中断周期:主 35Hz 子采样 100Hz(N=11932),总负载 < 5% 486(~50MHz)。
- 集成清单:
- 钩子 I_SoundOut(DOOM source sndserver.c)。
- 加载 pcsp 数据至音乐_buf。
- IRQ 中:decode_next_tone → update_phase → mix → out_pcspkr (sample>0 ? 1:0)。
- 监控:tick_count 溢出复位,FPS 降 < 30 禁用音乐。
- 回滚策略:检测 CPU<10MHz 降单通道;Linux 下 /dev/pcspkr ioctl 兼容。
工程监控要点:
- 性能:IRQ 耗时 < 50us/tick,游戏 FPS 稳定 35。
- 质量:失真 < 10%(方波谐波),动态范围 20dB(vol 阶梯)。
- 兼容:DOSBox/Xenial 模拟 PIT;真实硬件 286+。
此技术不仅复活 DOOM PC Speaker 音乐,还适用于复古合成器。开源 DOOM 源代码与补丁验证可行性,扩展至 Chocolate Doom 等端口。
资料来源:
- LenOwO 论坛帖子:id Software was Lazy - DOOM could have had PC Speaker Music!
- DOOM 源代码(sndserver,PIT 端口知识通用)。
(正文约 950 字)