ZX Spectrum 系统巡礼:声音
射击场演示完成后,是时候转向我们对 ZX Spectrum 的初步巡展中尚未涉及的一部分:声音。与其他平台相比,这里没有太多新的东西,但这也意味着我们对它的解决方案的核心原理已经预先准备好;我们只需要添加纯 Spectrum 特有的装饰。主线 Spectrum 系统提供两种声音选项:1 位软控制的蜂鸣器。这在所有系统中都存在,其底层硬件与我们在 Apple II 或 PC 扬声器上看到的相似。一个通用仪器 AY-3-8910 合成器芯片,存在于所有型号的 128K Spectrum 上,并作为外设可用于 16K 和 48K 变种。这些基本上与 Atari ST 中的相同,但时钟速度不同,我们没有内建的 BIOS 调用与之通信。这是我们总体调查的一部分,就像我们的文本模式和位图模式调查一样,所以我们的目标将是适度的;我们将以三个播放 C 大调音阶的程序结束这篇文章。通过 BIOS 使用蜂鸣器 1 位蜂鸣器与 Apple II 的非常相似;I/O 端口上的 $10 位可以被设置或清除以设置扬声器的状态。通过仔细计时切换该位之间的间隔可以产生特定音调。稍后我们将介绍执行此操作的代码,但我们可以暂时搁置它,因为我们不必自己实现——BASIC 中的 BEEP 命令已自动处理此操作,并通过转发到我们自己可以利用的通用例程来实现。BEEPER 例程位于内存位置 $03B5,实际上比我为 6502 系统编写的版本更聪明。HL 寄存器以 4 个周期的单位(空操作的长度)指定延迟时间,并通过一种与我们用于可编程精确 6502 延迟的技术相似的方法获得该精度。DE 寄存器指定音符的持续时间,这有点不方便,因为它实际上指定音符应该有的波长数量。长度相等但音调不同的音符需要不同的持续时间代码。而 BEEP 命令本身则依赖于提供中音 C 的半音音符和以秒为单位测量的持续时间。我们可以查看它在 $03F8 处的实现,以了解如何获取所需的值。这通过系统的浮点引擎完成,因此我们实际上从注释中得到了很好的公式:频率代码 HL 对于频率为 f Hz 的频率是通过公式 437500/f – 30.125 提供的。频率为 f Hz 并持续 t 秒的音符的持续时间代码 DE 简单为 f×t。几行 Python 让我们能够将这些应用于 C 大调音阶的频率,生成频率和持续时间代码以供该例程使用。以下是我们的播放程序:org $7000 ld b,8 ; 播放 8 个音符 ld hl,scale ; 从指针加载 loop: push bc ld c,(hl) ; 将频率加载到 BC inc hl ld b,(hl) inc hl ld e,(hl) ; 将持续时间加载到 HL inc hl ld d,(hl) inc hl push hl ; 存储指针... ld h,b ; ... 将 BC 复制到 HL... ld l,c call $03b5 ; ... 然后让 ROM 处理其余部分 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 例程足够好,如果我要编写自己的振荡器函数,我希望它基于完全不同的原则。幸运的是,我有这样一个原则可用:基于频率而不是半波长的 16 位系统,这是我创建时受到 SID 和 Ensoniq 芯片内部结构的启发。借助 Z80 对 16 位数学的内置支持,这应该比 6502 代码更直接。基本思路是重复将频率代码添加到一个运行计数器中,并在那个计数器溢出时切换扬声器。麻烦的地方是确保我们不干扰其他任何东西,因为 I/O 端口 $FE 执行三重职责:低三位设置边框颜色。我们想要保留 BORDC 中的任何值...
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡