ZX Spectrum 系统之旅:声音
摘要
关于 ZX Spectrum 声音编程的技术综述,涵盖 1 位蜂鸣器和 AY-3-8910 芯片,并提供演奏 C 大调音阶的示例。
暂无内容
查看缓存全文
缓存时间: 2026/08/03 19:34
# ZX Spectrum 系统之旅:声音
来源:https://bumbershootsoft.wordpress.com/2026/08/01/zx-spectrum-system-tour-sound/
既然打靶场已经完成,现在是时候转向我们最初 ZX Spectrum 之旅中尚未涉及的部分:声音。与其他平台上已有的内容相比,这里并没有太多全新的东西;但这反过来意味着我们解决方案的核心原则已经预先准备好了;我们只需要加上 Spectrum 特有的装饰即可。
主线 Spectrum 系统提供两种声音选项:
- 一个由软件控制的 1 位蜂鸣器。所有系统上都存在,其底层硬件与我们之前在 [Apple II](https://bumbershootsoft.wordpress.com/2016/09/05/making-music-on-the-apple-ii/) 或 [PC 扬声器](https://bumbershootsoft.wordpress.com/2016/12/10/beyond-beep-boop-mastering-the-pc-speaker/) 上看到的并无不同。
- 一颗 General Instrument AY-3-8910 合成器芯片,从 128K Spectrum 起的所有型号都有,并且可作为 16K 和 48K 版本的周边设备。它们与 [Atari ST 中的那颗](https://bumbershootsoft.wordpress.com/2021/05/02/atari-st-the-ay-3-8910-sound-chip/)基本相同,但时钟不同,而且我们没有内置的 BIOS 调用来与之通信。
这是我们概览式考察的一部分,就像我们的[文本模式](https://bumbershootsoft.wordpress.com/2026/05/30/zx-spectrum-system-tour-text-mode/)和[位图模式](https://bumbershootsoft.wordpress.com/2026/06/13/programming-the-zx-spectrums-bitmap-display/)考察一样,所以我们的目标不会太高;我们将在本文末尾用三个程序来演奏一段 C 大调音阶。
## 通过 BIOS 使用蜂鸣器
1 位蜂鸣器与 Apple II 的非常相似;I/O 端口上的**`$10`**位可以被置位或清零,以设置扬声器的状态。通过精确计时该位的翻转间隔来产生特定音调。我们稍后会讲到实现这一点的代码,但我们可以把这件事往后放,因为*我们不必自己动手*——BASIC 中的**`BEEP`**命令已经处理了这件事,它通过转发到一个通用的例程来实现,而我们可以自己利用这个例程。**`BEEPER`**例程位于内存地址**`$03B5`**(https://skoolkid.github.io/rom/asm/03B5.html),老实说它比我为 6502 系统编写的版本更聪明。**`HL`**寄存器以 4 个周期(一条空操作指令的长度)为单位指定延迟时间,而它获取这种精度的方式,与我们用于可编程、周期精确的 6502 延迟的技术颇为相似。**`DE`**寄存器指定音符的持续时间,这有点不方便,因为它实际上指定的是音符应包含的波长数量。长度相同但音高不同的音符需要不同的持续时间代码。
不过,**`BEEP`**命令本身依赖于以距中央 C 的半音数来表示音符,以及以秒为单位的持续时间。我们可以查看它自己的实现,位于**`$03F8`**,来了解如何获得我们需要的值。这是通过系统的浮点引擎完成的,所以我们实际上可以直接从注释中得到两个简洁的公式:
- 对于频率为 *f* Hz 的音调,频率代码`HL`由公式 *437500/f – 30.125* 提供。
- 对于频率为 *f* Hz、持续时间为 *t* 秒的音符,持续时间代码`DE`简化为 *f×t*。
用几行 Python 就可以将这些公式应用到 C 大调音阶的频率上,得到供例程使用的频率代码和持续时间代码。下面是我们播放程序:
```
org $7000
ld b,8 ; Play 8 notes
ld hl,scale ; Load from pointer
loop: push bc
ld c,(hl) ; Load frequency into BC
inc hl
ld b,(hl)
inc hl
ld e,(hl) ; Load duration into HL
inc hl
ld d,(hl)
inc hl
push hl ; Stash pointer...
ld h,b ; ... Copy BC to HL...
ld l,c
call $03b5 ; ... and let the ROM do the rest
pop hl
pop bc
djnz loop
ret
scale: dw $066a,$0082,$05b3,$0092,$0511,$00a4,$04c6,$00ae
dw $043d,$00c3,$03c4,$00dc,$0357,$00f6,$0325,$0105
```
这和我最初的 Apple II 声音播放例程一样属于“半波长”系统,但它精细得多的时序控制意味着音调的整体精度要*好得多*。基本上,我在最初那篇文章中面临的折衷在这里完全不存在,我们拥有专用 PSG 所应有的那种控制力。老实说,这真的很棒。这里唯一真正的缺点就是,像这样在两边都需要自己进行除法运算有点麻烦;如果我们想要一个管理任意频率和持续时间音调的通用例程,就需要自带除法器。不过,我们很快就会看到,在 16KB Spectrum 上我们没有其他选择,只能依赖这个例程。
不过,我们有点操之过急了。让我们先看看如何手动完成,然后再真正遇到在 16K 系统上会碰到的问题。
## 直接使用蜂鸣器
BIOS 例程足够好了,如果要我自己写一个振荡器函数,我希望它运行在一个完全不同的原理上。幸运的是,我正好有这样的原理:我受 SID 和 Ensoniq 芯片内部原理启发而创建的[基于频率而非半波长的 16 位系统](https://bumbershootsoft.wordpress.com/2024/10/19/a-musical-interlude/)。由于 Z80 内置了对 16 位运算的支持,这应该比 6502 代码直接得多。
基本思想是反复将频率代码加到一个运行中的计数器上,并在每次计数器溢出时切换扬声器。棘手之处在于确保我们不会干扰其他任何东西,因为 I/O 端口**`$FE`**身兼三职:
- 低三位设置边框颜色。我们希望保留**`BORDCR`**系统变量中的任何值。
- **`$08`**位控制的是音频输出,但那是针对磁带机的控制,而非扬声器。这些操作会以某种方式干扰电压,我在[平台指南](https://bumbershootsoft.wordpress.com/platform-guide-sinclair-zx-spectrum-and-friends/)中链接的 [Korth 文档](https://problemkaputt.de/zxdocs.htm)详细介绍了这些细节,但我们只需像系统 ROM 那样,始终强制该位为开启状态。
- **`$10`**位才是我们真正要操作的位,用于控制扬声器音频。
我们首先直接实现这一切,并尽量少用分支:
```
sound: ld hl,0 ; Counter starts at 0
ld a,($5c48) ; BORDCR
and $38 ; Extract border color
rrca ; and shift to low 3 bits
rrca
rrca
or $08 ; Disable tape audio
di ; Don't get interrupted by IRQ
push af
.loop: pop af
add hl,de ; Add frequency to counter
jr nc,1F ; If it overflows...
xor $10 ; ... toggle speaker output
out ($fe),a
1 dec bc ; De
相似文章
ZX Spectrum System Tour: Text Mode – Bumbershoot Software
This post explores the ZX Spectrum's text mode from a machine language perspective, covering system organization, display routines, and input handling, as part of a series on programming the classic 8-bit computer.
将我的3D点云渲染器移植到ZX Spectrum 48K上
一位开发者将3D点云渲染器移植到ZX Spectrum 48K上,通过Z80汇编优化达到每秒14帧,并创建了一个预计算版本,运行速度为每秒40帧。
在Amiga上零CPU占用播放Atari ST音乐
本文详细介绍了一种在Amiga上播放Atari ST音乐的技术,通过利用Amiga的PAULA芯片模拟YM2149音效芯片,实现零CPU占用,使CPU能够专注于其他任务,如渲染图形。
Boriel BASIC
Boriel BASIC 是一款现代开源的 BASIC 编译器 SDK,主要为 ZX Spectrum 设计,提供增强功能、整数类型以及内联汇编支持,适用于复古游戏开发。
EndBASIC 0.14:我们实现多媒体了吗?
EndBASIC 0.14 新增基本声音支持、新图形原语(三角形、多边形、填充)、复古 VGA 字体,并修复了 macOS 兼容性。