缓存时间:
2026/05/17 09:46
# 免费在 Amiga 上播放 Atari 音乐!
来源:https://arnaud-carre.github.io/2026-05-15-ym-fast-emu/
## 读者对象
本文适合所有热爱芯片音乐技术与历史的朋友,同样也适合 Amiga PAULA 和 Atari YM2149 音频芯片的发烧友。
## 背景
在我的 Cycle-Op 演示(https://www.pouet.net/prod.php?which=94129)中,我用一个经典的 sin-dots 特效(https://youtu.be/Mof0Cv4A98M?t=231)在 Amiga 500 上以 50 FPS 渲染了 **6405** 个点。两年后,Amiga 传奇人物 Hannibal 发布了期待已久的 3D Demo 3(https://www.pouet.net/prod.php?which=103979),其中包含大量令人印象深刻的效果。其中就有他自己的 sin-dots 记录,以 **6682** 个点超过了我的成绩。
为了锦上添花,并遵循演示场景中经典的友好调侃传统,Hannibal 给我留下了这样一条信息:
> “嗨,Leonard,你作为一个 Atari 程序员,点优化得不错。但如果你像 Amiga 专家那样优化,还能剩下好几百个点呢。”
这句话太有意思了,不能不理。我必须回应,想办法打破他新的 sin-dots 记录,并作为额外奖励,用一个巧妙的花招来回敬这句‘Atari 程序员’的调侃 :\)
## 目标
作为对 Hannibal 评论的回应,我立刻想到,在未来的点记录挑战中,在 Amiga 上播放 Atari 音乐。为此,我需要模拟 YM2149 声音芯片。其实我以前在我的 AmigAtari 演示(https://www.pouet.net/prod.php?which=85276)中就为 Amiga 写过 Atari 音乐模拟器。
然而,要精确再现现代 Atari 音乐效果,比如 SID 音色、Sync Buzzer 和 Digidrums,不仅需要模拟 YM2149,还需要模拟 Atari 的硬件定时器。这种模拟极其消耗 CPU,在我的 2020 年 AmigAtari 演示中占用了大约 50% 的帧时间。这使得同时打破 sin-dots 记录变得不可能。
所以我需要一个简单得多的方案:一种完全不使用 CPU 就能在 Amiga 上播放 Atari 音乐的方法。
我的第一个粗略想法很直接:为什么不直接用 Amiga 的 PAULA 芯片来模拟 YM2149,把可怜的 Motorola 68000 完全解放出来,专心画 sin dots 呢?
## YM2149 与 PAULA
从纸面上看,这两块芯片非常不同。
**YM2149** 是 AY-3-8910 的一个略微修改版,由雅马哈重新贴牌。它是一块相对简单的音频芯片,有三个只能产生方波的声道。有一个伪随机噪声发生器和一个基本的硬件音量包络,旨在作为廉价的 ADSR 包络(https://en.wikipedia.org/wiki/Envelope_(music)#ADSR)替代品。
**PAULA** 是 Amiga 的音频芯片,采用了完全不同的方法。它是一块 PCM 采样回放芯片,没有内置的方波、噪声生成或包络支持。PAULA 可以直接从主存中播放四个独立的 8 位有符号 PCM 采样,每个声道有自己的播放速率和音量。
对于一块 1985 年推出的芯片来说,这种设计相当先进。
## 第一个想法和原型
大多数 Atari 音乐严重依赖方波,这使得最初的概念看起来很简单。我将一个周期的方波存储为 8 位采样,然后使用一个 PAULA 声道循环播放。
为了避免将完整的 Atari 音乐驱动移植到 Amiga,我使用了一种类似于我为超快 Amiga MOD 播放器 LSP(https://github.com/arnaud-carre/LSPlayer)开发的技术:在专用的数据流中预先计算每帧的 PAULA 状态。
计划是:
- 编写一个 PC 工具,读取 Atari .sndh 音乐文件并逐帧模拟
- 以 50 Hz 的频率提取三个 YM 声道的周期和音量值
- 将这些值转换为 PAULA 兼容的周期和音量值
- 将结果存储在专用数据文件中
在 Amiga 端,播放变得极其轻量:每帧,程序只需读取几个值,更新三个 PAULA 声道的周期和音量,每个声道循环一段微小的方波采样。
## 第一个无聊的结果
用纯粹的方波能播放什么样的 Atari 音乐?试试非常古老的 ATARI 游戏 Buggy Boy 吧。
不怎么令人兴奋,对吧?无意冒犯原始的 Atari 移植版,但纯方波听起来相当平淡,肯定不够史诗,不足以伴随未来的 Amiga sin-dots 世界纪录。
我需要更好的东西。
## YM 包络技巧
到 1980 年代末期,几位才华横溢的演示场景音乐家发现了从 YM2149 中引出更丰富声音的方法。其中最有影响力的是 Jochen Hippel,也就是 TEX 的 MadMax。
YM 的包络硬件原本设计为简单的 ADSR 替代品,但很少按预期使用。主要原因是芯片只提供了一个包络发生器,在所有三个声道之间共享。这意味着所有声道必须使用完全相同的包络形状。
但富有创造力的音乐家发现了一个巧妙的变通方法。他们不是用包络来调制方波的音量,而是直接使用包络本身作为声源。这就产生了在许多经典 Atari 配乐(如传奇的 Thalion Software 开场)中听到的扫频、嗡嗡的质感:
**我可怜的 YM 到底是怎么发出这么猛的音色的??**
对于听着像 Buggy Boy 这样简单曲调长大的人来说,第一次听到这个简直是 revelation。这种独特的扫频效果没有官方名称,但为了本文,我们称它为 **MadMax Buzzer**。
## 它是如何工作的
YM2149 提供了几个预定义的音量包络形状,如下图所示。
其中一些形状是三角波,一些是锯齿波。
现在想象一下这样的设置:为声道启用包络,并配置为三角形。包络频率设置得足够高,落在可听范围内。(这作为传统的 ADSR 控制毫无意义,但现在包络本身就成为了声源。)
结果是一个类似三角形的可听波形。这听起来已经很棒了,但它仍然没有达到 MadMax 示例中那种激进和扫频的感觉。缺少的成分是什么?不是仅仅播放包络,而是在声道上同时启用方波,并使用轻微失谐的频率。
当方波信号为 0 时,输出为 0。当方波为 1 时,输出是当前的包络三角形状!结果是一个复杂而动态的波形,典型的 MadMax buzzer!让我们手绘示意一下
由于两个频率轻微失谐,就产生了那种动态、扫频的典型 MadMax 声音!现在你可以通过查看经典 MadMax 曲目“Leavin Teramis”中的 **voice B** 来了解这是如何工作的。注意由于失谐的方波,三角包络形状是如何随时间演变的。
*注意:三角波形看起来相当圆润,因为 YM2149 包络是对数而非线性的(并且只有 32 个离散值)*
## 如何用 PAULA 模拟这些包络技巧?
PAULA 是一块令人惊讶的芯片。很久以前我记得读过关于它可以调制周期或音量的功能。查看 PAULA 硬件手册,可以看到有一种称为“attached”声道的模式。在这种模式下,一个 PAULA 声道可以用来调制另一个声道的周期或音量。
在某种程度上,这个功能类似于 YM2149 的 ADSR 包络。不是技术上的相似,而是因为这两个功能大多数 Atari 和 Amiga 程序员都忽略了!:\)
据我所知,没有 Amiga 游戏使用它。而且我只知道一个演示(https://youtu.be/OK_U9UnZoNs?t=35)使用了音高调制选项。(可能是因为作为 ADSR 替代品不太实用:每调制一个声道就要消耗一个宝贵的 PAULA 声道)。相反,Amiga 追踪器使用 CPU 以音乐驱动速率驱动声道音量。这不如逐采样点音量调制精确,但对于游戏和演示来说已经足够好了。
YM2149 有 3 个声道。PAULA 有 4 个。那么为什么不用其中 3 个来模拟 YM 方波,而用第 4 个 PAULA 声道作为音量调制器呢?这听起来是个好计划!一个 Atari 程序员可能是最早利用这个被低估的 PAULA 功能的人之一 :\)
让我们看看 PAULA 手册中描述的调制器数据格式。
在正常模式下,每个 16 位字代表两个 8 位有符号 PCM 采样(蓝色圆圈)。在“音量调制”模式下,每个 16 位字代表一个 PAULA 音量值(绿色圆圈)。
结果,调制数据的运行速率是音频采样播放速率的一半(每两个 8 位采样对应一个音量值)。后面会有更多讨论。
在 Amiga 内存中,我们现在有一个 8 位方波:
``
AmigaSquareWave:
dc.b $7f,$7f,$7f,$7f,$7f,$7f,$7f,$7f,$00,$00,$00,$00,$00,$00,$00,$00
``
*注意:我没有使用全范围的方波(-128..+127),而是用了 (0..127),这样半个周期是 0 而不是 -128,以便在被包络调制时正确产生 0 输出*
以及一个 16 位的三角波,代表音量值。(记住音量调制数据是 16 位的,尽管它只包含 [0..64] 范围内的 PAULA 音量值)
``
AmigaVolumeModulatorTriangleWave:
dc.w 0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3
dc.w 4,5,6,7,9,11,13,15,19,22,26,31,38,45,53,64
dc.w 64,53,45,38,31,26,22,19,15,13,11,9,7,6,5,4
dc.w 3,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0
``
*注意:它不是线性的三角形,因为 YM2149 音量的对数特性*
当我的 PC 离线工具检测到某个声道同时有 YM 包络和方波时,Amiga 重放器将启用这种 PAULA attached 模式,并使用来自声道 0 的 16 位三角波来调制声道 1 的音量。让我们通过隔离 **Leavin Teramis, song 9** 中的 MadMax buzzer 来做个测试。
首先是音频参考,使用我的 Atari 音频模拟库(https://github.com/arnaud-carre/sndh-player/tree/main/AtariAudio)在 PC 上生成。
您的浏览器不支持音频元素。然后是通过 WinUAE Amiga 模拟器输出的仿真结果,使用了奇怪的 PAULA “attached voice” 功能:
您的浏览器不支持音频元素。天哪,这声音真难听。完全不像参考音……为什么呢?
## 音频调试
让我们用 Audacity 看看这两个输出。
这是来自 AtariAudio PC 模拟库的参考:
而现在使用“attached voice”调制技巧的 Amiga WinUAE 输出:
我们可以清晰地发现问题所在。调制包络看起来很粗糙:期望的平滑三角波分辨率非常低,有大的、明显的音量阶梯。
这很容易解释。回想一下,在 attached voice 模式下,每两个输出采样只有一个音量值。当我们以所需的音乐速率对三角波进行采样时,许多中间值被直接跳过。结果就是这种不连贯的波形。
我想了很久,尝试调整波形的分辨率、调制器的音高和其他参数。没什么真正奏效。无论我怎么尝试,调制器数据的低速率总是产生同样粗糙的结果。
我几乎要放弃了,打算为未来的 sin-dots 记录使用经典的 Amiga MOD……
然后……
## “尤里卡”时刻!
有一天晚上躺在床上,主意来了!(我注意到大多数“尤里卡”时刻恰好发生在即将入睡的时候 :\) )
还记得 Atari 音乐家们是如何开创性地使用 YM2149 包络,以原始芯片设计者从未想过的方式吗?为什么不对 PAULA 也这样做呢?
attached voice 的低频数据本应是包络形状,用来调制一个 8 位方波信号。但如果我们简单地交换角色呢?我们把三角包络存为 8 位采样,而使用一个方波“音量”波作为调制器。结果应该相同,但方波并 **不** 需要三角波那样的高分辨率!
只需改几行代码。现在方波存储为 16 位调制器格式(高为 64,低为 0)。
``
AmigaSquareWaveAsModulator:
dc.w 64,64,64,64,64,64,64,64,0,0,0,0,0,0,0,0
``
而三角包络形状将由标准的 8 位 PCM 播放,分辨率更高(0..+127)
``
AmigaVolumeModulatorTriangleWaveAs8bitsPCM
dc.b $00,$00,$00,$00,$01,$01,$01,$01,$02,$02,$03,$03,$04,$05,$06,$07
dc.b $09,$0b,$0d,$0f,$12,$16,$1a,$1f,$25,$2c,$35,$3f,$4b,$59,$6a,$7f
dc.b $7f,$6a,$59,$4b,$3f,$35,$2c,$25,$1f,$1a,$16,$12,$0f,$0d,$0b,$09
dc.b $07,$06,$05,$04,$03,$03,$02,$02,$01,$01,$01,$01,$00,$00,$00,$00
``
让我们看看 WinUAE 的输出吧!
并听听新的 AMIGA 输出!
您的浏览器不支持音频元素。成功了!
## 最终实现 0 CPU
到目前为止,一切顺利:我们可以用那种类似 MadMax buzzer 的声音播放优美的 Atari 音乐了。我们的播放器也非常快(不到一条光栅扫描线),因为每帧只需要设置几个 PAULA 寄存器。但我们不是承诺过 **“0% CPU 负载”** 吗?
Amiga 是一台了不起的机器,装满了自定义芯片。其中之一是 COPPER。COPPER 是一个非常简单但极其强大的协处理器,与 68000 CPU 完全并行运行。它执行来自“COPPER 列表”的自己的指令,并且只支持两种操作:
- 向任何 Amiga 自定义芯片寄存器(包括 blitter、颜色、屏幕、PAULA)写入一个立即值
- 等待特定的屏幕位置
那么,为什么不预生成大量微小的 COPPER 列表,每个音乐帧一个呢?每个列表将包含所有对 PAULA 寄存器所需的“写”命令。这将完全解放 68000。由于 COPPER 还可以更新指向下一个 COPPER 列表的指针,我们可以构建一个完全自动、自链接的序列,让它在没有 CPU 干预的情况下自行播放 Atari 音乐。
最终,YM2149 模拟器真正使用了 **0% 的 CPU**!甚至连 **一条 68000 指令** 都不用!:\)
## 最终成果
以下是新的 sin-dots 记录成果!我们成功显示了 **7210** 个点,而之前是 6682 个,同时还播放着 Atari 音乐!还不错,对吧?:\) (我可能会再写一篇博文解释如何达到这个 7210 点的结果。)
另外,请欣赏 BigAlec 创作的精彩 Atari 音乐,它完美展示了类似 MadMax buzzer 的效果!
您也可以在这里下载演示(https://www.pouet.net/prod.php?which=104190)。
享受吧!