Skip to content

[AI] 用 AI 向开源项目贡献:为 v86 模拟器实现完整 SB16 音频支持 | Contributing to Open Source with AI: Implementing Full SB16 Audio in the v86 Emulator #88

@luckyyyyy

Description

@luckyyyyy

这篇文章,也是 AI 写的。


背景:v86 是什么

v86 是一个用 JavaScript/WebAssembly 实现的 x86 PC 模拟器,能在浏览器里直接运行 DOS、Linux、Windows 等操作系统。它已经能跑很多老游戏,但长期以来,音频支持一直是个短板——特别是 SB16(Sound Blaster 16)这块经典声卡的模拟,有大量功能是缺失或错误的。

我提交的这个 PR:copy/v86#1517,试图补全其中最核心的一批缺口。


以前人工做这件事有多难

SB16 是 90 年代最主流的 PC 声卡,几乎所有 DOS 游戏都针对它做过适配。它包含了好几个相互独立又彼此关联的子系统:

1. OPL2/OPL3 FM 合成器

OPL 是 Yamaha 开发的 FM 合成芯片,SB16 内置了 OPL3。它的工作原理是用多个"运算符(operator)"相互调制来合成音色,涉及 ADSR 包络、KSR(键值比例)、颤音、抖音、波形选择等大量参数。

以前 v86 用的是 ScriptProcessorNode(已被 Web 标准废弃),精度低、延迟高。要把它替换成现代的 AudioWorklet 并正确实现所有 OPL3 特性,需要:

  • 深度理解 DOSBox 的 DBOPL 源码(C++,几千行)
  • 将其逻辑移植到 JavaScript 的 AudioWorkletProcessor 上下文中
  • 处理 OPL3 的 4-op 通道、立体声声相、Bank-1 地址转发等扩展模式
  • 将 OPL 输出正确接入混音器,使音量寄存器生效

仅这一项,如果纯手工完成,至少需要一位既懂 Web Audio API、又熟悉 FM 合成原理、又读过 DOSBox 源码的工程师,耗费数天甚至更长时间。

2. Creative ADPCM 解码

SB16 的 DSP 支持 Creative 自研的 ADPCM 压缩格式,分为 2-bit、3-bit、4-bit 三种,每种又有"带参考字节"和"自动初始化"两个变体,合计 9 条 DSP 命令需要实现。

这些格式没有广泛的开源实现可以直接参考,需要查阅 Sound Blaster 硬件编程指南原始文档,逐位推导解码逻辑。

3. DSP 命令集

SB16 的 DSP 有 80 多条命令,每条的行为都有细微差异,很多文档记录不完整,只能对照 DOSBox 源码推断正确行为。比如:

  • 命令 0x80(静音 DAC):需要根据采样率计算延迟,用 setTimeout 模拟后触发中断
  • 命令 0x24(ADC 采集):v86 没有录音硬件,但驱动会等它完成;需要伪造一段静音数据填入 DMA 并触发中断,否则游戏卡死
  • ASP 寄存器(0x04/0x05/0x08/0x0F):纯粹是初始化握手,文档几乎没有,只能跟着 DOSBox 的处理逻辑走

4. MPU-401 MIDI 接口

MPU-401 是 Roland 出的 MIDI 接口卡,SB16 内置了兼容实现。很多 DOS 游戏用它输出 MIDI 音乐。它有"UART 模式"和"智能模式"两种,驱动初始化时会先发 reset 命令,再切换到 UART 模式。

如果没有正确实现握手(ACK 字节 0xFE),驱动会死等,导致游戏无法启动。

5. 混音器寄存器

SB16 的混音器有 30+ 个寄存器,每个对应不同的音量/路由控制。其中很多在 v86 里只是 TODO 的空桩,格式也不统一——有些是 val >> 3 移位存储,有些共享同一个内部变量,细节全在硬件手册里。


难在哪里

把上面这些放在一起,难点不是"某一项很难",而是:

  1. 知识面极宽:需要同时掌握 Web Audio API、x86 硬件架构、DOS 声卡历史、FM 合成原理、DMA 传输机制
  2. 文档残缺:很多行为只能从 DOSBox 源码反向推导,没有权威文档
  3. 调试成本极高:一个寄存器写错,可能是游戏卡死、没声音、或者声音跑调,定位根因需要大量对照测试
  4. 上下文极长:每改一处都需要理解整个 SB16 状态机的上下文,牵一发而动全身

一个熟练工程师从头做这件事,保守估计需要 2~4 周,还不包括测试各种 DOS 游戏的兼容性。


AI 为什么能做到

这个 PR 的大部分代码,是我用 AI(GitHub Copilot / Claude)辅助完成的。整个过程大概花了 不到两天

AI 在这里有几个明显优势:

1. 知识广度不是瓶颈

FM 合成、ADPCM 格式、MPU-401 协议——这些对人类工程师来说是需要专门学习的领域知识,但对 LLM 来说,这些都在训练数据里。我不需要自己去读 Yamaha OPL3 的数据手册,只需要描述我想实现什么,AI 就能给出正确的位运算和状态机逻辑。

2. 代码翻译效率极高

把 DOSBox 的 C++ 逻辑翻译成 JavaScript,这件事对人工来说很繁琐(语法转换、内存模型差异、类型系统不同),但 AI 做代码翻译几乎是零成本的。

3. "对照 DOSBox 行为"是天然的 prompt

很多命令的正确行为就是"跟 DOSBox 一样"。我可以直接把 DOSBox 的 C++ 源码粘给 AI,告诉它"用 JavaScript 实现同样的逻辑,适配 v86 的架构",它就能给出可用的代码。

4. 并行处理多个子系统

我同时推进了 OPL、ADPCM、DSP、MPU-401、混音器五个方向。对人工来说,这需要在多个复杂上下文之间来回切换,极易出错。AI 每次都能在清晰的上下文里独立完成一个子任务,然后我再整合。


这说明了什么

这件事让我意识到,AI 在开源贡献这个场景里有一种特别的优势:

开源项目的 hard part 往往不是"思考",而是"读懂存量代码 + 消化领域知识 + 把细节都对齐"。

这三件事,正是 AI 最擅长的。

以前,向一个成熟开源项目贡献一个复杂功能,门槛极高——你需要读懂整个项目的架构,掌握相关领域的背景知识,还要有足够的耐心处理所有边界情况。这个门槛让很多人望而却步。

现在,AI 把这个门槛降低了一个数量级。我不需要是 SB16 专家,也不需要是 Web Audio 专家,只需要能看懂代码、能描述问题、能判断 AI 输出的对错——这件事就能做成。


最后

PR 还在 review 中,能不能合并还不一定。但这个过程本身让我觉得很有意思:

一个我以前完全不会碰的项目,因为有了 AI,我居然可以做出有实质内容的贡献。

这不是"AI 替代了我",而是"AI 让我能做到以前做不到的事"。

这个区别,我觉得值得记录下来。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions