Digital Audio on the ZX Spectrum's 1-Bit Beeper
Achieving Digital Audio via the ZX Spectrum's 1-Bit Beeper
This article serves as the conclusion to a series of software experiments by Michael Martin (Bumbershoot Software), focusing on pushing the limits of the ZX Spectrum's audio capabilities.
The goal was simple yet ambitious: force the ubiquitous, primitive 1-bit beeper found on all Spectrum models to produce high-fidelity digital audio. The end result is a stark contrast to previous PCM playback methods—the difference is, quite literally, night and day.
🛠️ Verification and Compatibility
To ensure the code wasn't just a fluke of emulation, hardware verification was conducted. Thanks to the community, the demo was tested on:
- Original 48K Spectrum (Verified by "gmc" via Mastodon)
- Omni 128 (A modern system rebuild, verified by Tom Harte)
Furthermore, the implementation is highly compatible across various emulators.
| Emulator | Status |
|---|---|
| Fuse | (Used for demo capture) |
| EightyOne | |
| Zesarux | |
| Spectaculator | |
| Clock Signal |
⚠️ The Trade-offs
While the technique works, it isn't without its drawbacks:
- Volume: Without an external amplifier, the audio is very quiet.
- Memory: The RAM requirements for samples of this quality are immense—essentially consuming
all of it.
💻 The Engineering Struggle
Getting this to work required two complete rewrites. The timing constraints were incredibly rigid, forcing me to utilize techniques I hadn't previously employed on this architecture.
While I typically prefer C when compiler support is robust (similar to the DOS era), this project demanded Assembly Language. The reasons were twofold:
- Cycle-Exact Timing: The audio effects required instruction sequences timed to the clock cycle.
- Resource Constraints: The "fierce register pressure" and the need for direct chip access made higher-level languages impractical.
I initially thought this was the hardest version of the problem; in reality, there are more sophisticated ways to improve the technique, though they weren't necessary for these specific tests. For those interested in similar challenges, I recommend looking into Dmitry Milk's sound engines or Michael J. Mahon's RTSynth for the Apple IIe. Despite different CPUs, their approaches are more similar to each other than to mine.
📚 Theoretical Foundation & Prior Art
My interest in 1-bit sampling began with 1990s DOS gaming. I recall:
- Eric the Unready: Used a system called
RealSoundto play effects through the PC speaker. - Star Control II: The Ur-Quan Masters: Ported Amiga-style tracker music to the PC speaker.
The PC vs. The Spectrum
Programming the PC speaker is significantly easier. While both use I/O ports to toggle the speaker, the PC possesses a highly programmable hardware timer. This timer acts as a Digital-to-Analog Converter (DAC) by allowing the programmer to set microsecond-precise pulse widths without the CPU having to "babysit" the speaker in a tight loop.
The Physics of Sound
The core principle relies on the physical properties of the hardware:
- Capacitance: Circuits have inherent capacitance.
- Mass: Speaker cones have physical mass.
Because of these, a signal doesn't jump from to instantaneously. By interrupting the signal mid-transition, we prevent the cone from reaching its full excursion, creating a more nuanced, varying wave.
⚙️ The Implementation Logic
Since the Spectrum lacks a dedicated timer for sample rates or pulse widths, everything must be handled in software. The logic follows a specific loop:
The key is that Delay 1 + Delay 2 must always equal a constant value to ensure the sample rate remains stable regardless of the pulse width.
📉 Misguided Attempt 1: The Math
My first attempt relied on "napkin math" based on my old PC system, which used pulse widths from to .
For an sample rate, the window for each pulse is:
I noticed a red flag here: my test program used a rate, meaning I would overrun the timing if the sample used the full range. Regardless, I proceeded with the calculations for the Spectrum's CPU:
Thus, I had roughly 438 cycles to execute the entire loop per sample.
Visual References
Figure 1: Sawtooth digital output
Figure 2: Voice clip sample
Images provided by Bumbershoot Software.
