Spots

Digital Audio on the ZX Spectrum’s 1-Bit Beeper

It’s finally time for me to wrap up this ZX Spectrum series, with a look at how to get the simple 1-bit beeper on every Spectrum model to emit high-quality digital audio. The results are quite good: it’s the last sound played on last week’s demo reel, and the difference between it and the simple PCM playback system before it is night and day.

I have also, at this point, gotten hardware

I have also, at this point, gotten hardware verification of the demo program on both period hardware and modern clones: the vagaries of the Internet mean that identification is usually by handle, but “gmc” via Mastodon was able to run it on a 48K Spectrum, and Tom Harte got results from the Omni 128 system, which is a modern rebuild of the system. Emulation support is also quite widespread; I used Fuse to capture the demo above, but it also works fine on, at minimum, EightyOne, Zesarux, Spectaculator, and Clock Signal.

The practicality of the technique is a bit

The practicality of the technique is a bit limited; the sound is very soft on hardware without an external amplifier, and the RAM required for samples of the length and quality I’m experimenting is effectively “all of it.” But it does work.

Getting it to work, though, was a journey

Getting it to work, though, was a journey. It took me two complete rewrites before I landed on something that worked at all, and even then it barely fit into the timing constraints I’d set for myself. I had to pull out several techniques I haven’t used before on this system. I mostly work in assembly language here, just because it’s generally more comfortable to work there on the systems I’m playing with—as long as there’s suitable compiler support like there was on DOS you usually can do just fine in C. Today, I would not have done just fine in C. Even leaving aside how I needed exactly timed instruction sequences for the audio effects, the timing constraints were so tight and the register pressure so fierce that I needed direct chip access to make it work.

This isn’t even the hardest form of the

This isn’t even the hardest form of the problem, either, and there are some straightforward ways to improve the technique I use here which I don’t need for my tests. I’ll be wrapping up today by looking at Dmitry Milk’s Spectrum sound engines as well as Michael J. Mahon’s RTSynth system for the Apple IIe; they’re solving a slightly different problem from the one I faced, so even though our sound output approaches are similar, they end up more similar to each other than to my playback system despite being on totally different CPUs.

I first ran into 1-bit digital samples by

I first ran into 1-bit digital samples by way of early-1990s DOS games. In particular, the illustrated text adventure Eric the Unready could play sound effects through the PC speaker with a technology its manual called “RealSound,” and Star Control II: The Ur-Quan Masters used Amiga-style tracker music for its soundtrack and could deliver it out your PC speaker just as cheerfully as it could your SoundBlaster.

The PC’s 1-bit speaker is markedly easier to

The PC’s 1-bit speaker is markedly easier to program than the Spectrum or the Apple II; I went through both simple tone generation and PWM playback about a decade ago. Everything there is still functional and accurate as far as I know, but we don’t really need it for what we’re doing today. Its approaches do motivate our design and strategy, though.

The fundamental physical principles here are that circuits

The fundamental physical principles here are that circuits have capacitance and speakers have mass, so it takes time for a 1-bit signal to actually travel from 0 to 1 or vice versa, and then it takes more time for the speaker’s cone to actually move across its own space to actually produce the sound waves we hear. If we interrupt the signal partway through, the signal and the speaker won’t make it all the way across the space and we’ll get a more finely-varying sound wave.

This was unreasonably easy on the PC—while the

This was unreasonably easy on the PC—while the speaker’s setting can be directly commanded via an I/O port the way the Spectrum’s is, it could also be driven from a highly-programmable hardware timer that ran independent of the system clock interrupt. This meant that instead of having to constantly baby-sit the beeper with cycle-counted code, you could just configure timing information only when you needed to change the signal being sent. For normal beeping that’s just when you’re changing frequencies, but you could also configure one-shot pulse widths with microsecond precision. That meant that you could set a CPU timer interrupt at 16kHz, or whatever your sample rate was, and then set the sound timer to run for a time based on that sample’s PCM value. The timer chip itself thus ended up serving as a sort of digital-analog converter.

We don’t have a timer on the Spectrum

We don’t have a timer on the Spectrum, either for enforcing a sample rate or for configuring a pulse width, but we should be able to do both of those things in software directly. The general idea will be to have a loop that lasts as long as each sample, and it will turn the speaker on then off each loop, with two short but variable-length delays on either side of “turn the speaker off again.” The first delay sets the pulse width, and the second makes sure that the total block of code takes the same amount of time no matter how wide the pulse was. Misguided Attempt 1: Actual Delay Loops My first implementation attempt started with some napkin math. My PC playback system accepted pulse widths that ranged from 1 to 127 microseconds.

News

Digital Audio on the ZX Spectrum’s 1-Bit Beeper

It’s finally time for me to wrap up this ZX Spectrum series, with a look at how to get the simple 1-bit beeper on every Spectrum model to emit high-quality digital audio.

@spots
Source: Hacker News
See more like this