这篇文章,也是 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 移位存储,有些共享同一个内部变量,细节全在硬件手册里。
难在哪里
把上面这些放在一起,难点不是"某一项很难",而是:
- 知识面极宽:需要同时掌握 Web Audio API、x86 硬件架构、DOS 声卡历史、FM 合成原理、DMA 传输机制
- 文档残缺:很多行为只能从 DOSBox 源码反向推导,没有权威文档
- 调试成本极高:一个寄存器写错,可能是游戏卡死、没声音、或者声音跑调,定位根因需要大量对照测试
- 上下文极长:每改一处都需要理解整个 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 让我能做到以前做不到的事"。
这个区别,我觉得值得记录下来。
背景: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 特性,需要:仅这一项,如果纯手工完成,至少需要一位既懂 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 并触发中断,否则游戏卡死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移位存储,有些共享同一个内部变量,细节全在硬件手册里。难在哪里
把上面这些放在一起,难点不是"某一项很难",而是:
一个熟练工程师从头做这件事,保守估计需要 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 让我能做到以前做不到的事"。
这个区别,我觉得值得记录下来。