If there's one thing the ZX Spectrum excelled at, it was the price. Compared to its contemporary (and way more capable) c64, according to one source it cost 130 GBP compared to 196 GBP for the c64 in 1984, plus you practically had to buy the disk drive for the c64 as well, and that cost as much as the computer itself.
To put this in perspective: In modern 2015 money, inflation-adjusted, the speccy would have cost around 530 EUR, while c64 with the disc drive would be a whopping 1600 EUR.
Software distribution of the speccy was plain old C-cassettes, and for the young ones out there, these used to be as common as USB sticks are today, if not even more common. Since they were common, so were tape players, and thus you most likely didn't need to buy one to play with your speccy, you just hooked your existing one in.
And the tapes (due to unreliability of the media and the players) had the data on them at a low speed of 1200bps. Let's say your game takes 32kB. 32 * 1024 * 8 / 1200 = ~220 seconds, or over 3 and half minutes. Add a half-minute loading time for a loading screen. You kids DON'T KNOW ANYTHING about loading times.
The c64 had custom graphics chip with hardware sprites and a synthesizer audio chip that's forever unsurpassed in a home computer, multiple graphics modes and all sorts of things you can do to push the boundaries of what's obvious.
The spectrum.. well. Audio is produced through a piezo speaker wired to a single pin of the ULA chip - a precursor of FPGAs - that is also responsible for video output. (Yes, c64 had several custom ASICs, the speccy had an ULA - they were a world apart). There's just one graphics mode, which is not double buffered. The ULA chip also takes priority over the z80 CPU when accessing memory, so while the display is being updated (which is most of the time), the CPU is running slower.
The speccy's only graphics mode is 256x192 bitmap (as in 2 colors), with an 32x24 color overlay, meaning that for each 8x8 pixels of the bitmap, you can define the background ("Paper") and foreground ("Ink") color. The name "Spectrum" came from the color output, which was new compared to Sinclair's earlier ZX81 computer. No idea what the ZX stands for. Probably sounded cool. It was the 80's, remember.
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
F | B | P | P | P | I | I | I |
Both Paper and Ink have three bits, for red, green and blue. Thus, there's just 16 colors. Or actually, there's 15 colors, because bright black is still black. There's only one "Bright" bit, meaning that the cell is either bright or not, you can't have dark background and bring foreground or vice versa. It's all or nothing. Additionally there's a "Flash" bit, which causes the back and foreground colors to swap every 16 frames. Useful.
What happens if your sprite is not aligned to 8 bits? Color clashes. No wonder why so many speccy games were pretty monochromatic.
Since the display offset can't be changed - you literally can't modify the graphics mode in any way - the machine is singly buffered. In modern terms, if you draw to the screen, you draw to the foreground buffer. If you want to be frame-synced, i.e, draw 50 frames per second, you don't have a choise but to race the beam. Personally I'd rather not, and I'll probably try to avoid doing any really critical timings, but we'll see.
Syncing to the frame is dead easy though. You just enable interrupts (assuming you've disabled them for some reason) and hit the HALT instruction. The HALT waits until the interrupt, which happens at the start of the frame. Yes, I said "the interrupt" here, that's the only interrupt you'll get. No, you can't reprogram it to trigger more often.
The interrupt has three different modes, one jumps into ROM, another jumps into a specified address (of a small subset), and third jumps to a (more or less) random address. The ROM one is default, and does some keyboard scanning and stuff which takes variable amount of time, so you may want to use the second one, do exactly what you want per frame and return. The third one is based on what data is in the data bus at the time, which may be useful in some other hardware application than the ZX Spectrum.
Let's talk audio. I said it's hooked to a pin of the ULA. You can change the state of the pin by hammering a bit on the port 254. In a busy loop. While doing nothing else. Eating precious frame time.
I've analyzed a bunch of speccy games to find out how they do audio, and most spend about 20%-40% of the frame time playing sound. Well, most of the ones that even try to run at 50Hz. Most games, you see, don't even try. They solve the whole issue by running at some arbitrary frame rate, and let their sprites blink a bit, solving several problems in one go.
There's been better audio from speccy (and some people keep pushing the envelope) but those are achieved by spending pretty much all of the CPU just playing sound, so they are only useful for main menus or whatnot.
The speccy's CPU runs at 3.5MHz, which sounds like a lot compared to c64's 1MHz 6510, but once you realize that every single instruction takes at least 4 clocks, most taking more, it's not really that impressive. Add to that the fact that whenever the ULA wants to access the memory (to draw the image on screen) you may end up waiting up to 6 clocks more, and, well. It's slow.
It's actually so slow that you can memcpy about 64 scanlines in a frame. So double buffering through an off-screen buffer is out, as are many other ideas.
To add further insult to injury, the display bitmap isn't linear.
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
C0 | C1 | C2 | Y7 | Y6 | Y2 | Y1 | Y0 | Y5 | Y4 | Y3 | X4 | X3 | X2 | X1 | X0 |
Where Cn are constant bits, always set to 0, 1 and 0. Luckily the bottom bits have the horizontal coordinates (5 bits = 2^5 = 32, 32 * 8 = 256), which means one scanline is stored linearly. The vertical bits are shuffled a bit so that the ULA doesn't need to do unnecessary work when mixing the bitmap and the color overlay.
There's some code out there to do a conversion from linear coordinates to the above-mentioned mess, but due to the very limited instruction set of the z80 (Barrel shifter? What barrel shifter?) it's far from trivial. In practise the simplest solution is to sacrifice 192*2=384 bytes for scanline offset table. I wonder how common said table is in all of the software out there, and whether they considered putting one in ROM.
So there you have it, the infernal machine in a nutshell.
Or well, not exactly.
I didn't mention about setting the border color or reading the keyboard yet.
Remember the port 254 that had a bit for audio? Yup, you use the other bits of said port to set the border color and to read the keyboard. I haven't (yet) played with keyboard I/O, so I'm not ready to write about that yet.
But anyway. Till next time.