==================================================================== THE WAR OF THE WORLDS: A DYNAMIC TAPE LOADER E-BOOK FOR FAST READERS (C) EINAR SAUKAS - WOOT 2023 ==================================================================== This "tech demo" is a custom dynamic tape loader e-book for the ZX Spectrum. It displays on screen the complete text of the book "The War of the Worlds" (by H. G. Wells) while loading it from tape. At standard tape speed, the text appears too fast to read comfortably (unless you are a speed reader), but it wouldn't be an impressive tech demo otherwise... Although when using an emulator, there's the option to slow down emulation or pause after each page. Technically the ZX Spectrum stores data on tape as a sequence of pulses. In a standard loader, bit 0 is stored as a pair of 855T pulses, bit 1 is stored as a pair of 1,710T pulses. The duration of each pulse can be identified by its "edge", i.e when the tape signal changes from ON to OFF, or vice-versa. The original tape loader routine contains a small pause of 354T before it starts looking for the next pulse "edge" (to measure each pulse duration). This custom loader simply uses this time to execute some useful work instead. There's considerable tolerance in loader timings to accomodate tape recorder imprecisions in recording/playback speed. However modifying these timings is very tricky, since it requires lots of practical testings with many physical tape recorders in order to evaluate the reliability of any new custom tape loader. To avoid this kind of problem, this custom loader was developed under a very draconian restriction: every possible execution path takes EXACTLY the same time as the original standard loader, except allowing (at most) a single contended memory access per pulse (which means only between 0T and 7T extra delay depending on the exact position of the TV raster scan). This restriction allows changing (at most) one screen memory byte per pulse. Since reading each character from tape takes 16 pulses (2 x 8 bits), it's possible to modify at most 16 memory addresses on screen for each byte loaded from tape. It means that, while a new byte is getting loaded, there will be enough time to display a previous character code on screen (which requires copying 8 pixel bytes, erasing a position underneath and moving the cursor). However there won't be enough time if the previous code was an ENTER, since it would require hiding the cursor and/or erasing at most 32 positions on screen. Therefore if a few consecutive ENTER codes are loaded from tape, it will make the output start "lagging" a couple characters behind. Afterwards the routine should take some time (while loading and printing the next few characters) to catch up. Because of this, this custom tape loader uses a small circular input buffer (of 256 bytes) where it stores new loaded codes while previous codes are still being drawn on screen. In a nutshell, drawing each loaded code takes multiple steps. Each step is executed during the 354T idle interval mentioned above. This sequence of steps is controlled by a finite-state machine, as follows: +-------+ | | V | +----> st_enter --+ \ | \ | (ENTER)\ | (non-ASCII) \ | +-----------+ +------+ \ | / | | | \ | / | | V (CHAR) \ V / V +-- st_char <------------- st_main <-------- st_cursor <--+ | | | | | | | (SPACE)| | | | | V V | st_show -------------> st_erase ----------------------+ The finite-state machine steps are: * "st_main": Check next byte from input buffer and decide what to do, choosing the next step. * "st_char": Draw a printable character at current screen position, one pixel line at a time. This step is always executed 8 times and, to avoid showing an incomplete character on screen, the current position is always hidden using same INK/PAPER attributes. * "st_show": Modify current screen attribute to make it visible. This way, each recently printed character "pops" instantly on screen. * "st_erase": In order to always keep a blank row of separation from any old text still visible on screen, whenever a screen position is modified, the position immediately below is erased (i.e hidden using same INK/PAPER attributes). * "st_cursor": Display text cursor at new screen position. * "st_enter": Erase next position on screen, modifying color attributes to hide it. This step is repeated until the end of current line, in order to represent ENTER (code 13) on screen. For more detailed information about the implementation of this custom loader, take a look at the commented source code. ======= THE END =======