QRL

Developer(s): Jack Dohany
Date: 1986
Type: Program
Platform(s): TS 2068
Tags: Graphics

QRL is a run-length encoding (RLE) image viewer and editor that manages compressed graphics stored at address 37000, with its machine code routines loaded starting at address 30400. The program uses a suite of USR calls into machine code blocks referenced by calculated addresses (STB, BTS, ECD, DCD, CPF, INV, TLN, WIP, LAD) that handle screen blitting, encoding, decoding, inverting, and wiping operations. A cassette-based save/load system supports three data types: RLE files, raw screens (6144 bytes from address 16384), and the BASIC program itself with its machine code payload. The WIPE routine (line 700) iterates over the screen’s bitmap in a specific band-row-column order, poking each line’s address into a parameter area before calling the USR WIP machine code routine, effectively wiping a user-specified number of pixel rows. Input handling normalizes lowercase to uppercase by checking whether the key code exceeds 90 and subtracting 32, and a debounce loop at line 410 waits for the key to be released before acting.


Program Analysis

Program Structure

The program divides into several functional regions:

  1. Lines 1–35: Initialization — clears memory, dimensions the display string E$, sets color variables C1/C2, calculates all machine code entry-point addresses, and calls the screen-setup USR routine.
  2. Lines 40–160: Main loop — calls the “total lines” USR (TLN) to get the current RLE file size into L, optionally draws the menu, then polls for a keypress and dispatches on it.
  3. Lines 190–291: Load sub-system — handles loading an RLE file to address 37000 or a raw screen to address 16384, with a shared filename-input subroutine at line 290.
  4. Lines 300–394: Save sub-system — handles saving an RLE file, screen (6144 bytes), or the full BASIC program with machine code payload, plus optional verify.
  5. Lines 400–430: Key-input routine — busy-waits for a keypress, beeps, debounces, uppercases, then jumps to the address stored in RET.
  6. Lines 500–510: Menu drawing subroutine — prints a three-row option grid inside a PLOT/DRAW border box.
  7. Lines 700–740: Wipe routine — iterates over screen memory in band/row/column order, poking the address of each pixel row into the machine code parameter area and calling the USR WIP routine.
  8. Lines 900–920: Post-load trampoline — reloads the machine code block and calls a buffer-clear USR before restarting.

Machine Code Interface

All machine code lives in a block starting at STB = 30400. Named entry points are calculated at line 10 as fixed offsets from STB:

VariableAddressPurpose
STB30400Screen-to-buffer blit / display restore
BTS30403Buffer-to-screen blit
ECD30406Encode screen to RLE
DCD30409Decode RLE to screen; returns success flag
CPF30412Copy/flip routine (used on COPY key)
INV30415Invert image
TLN30418Return total line count / RLE data length
WIP30421Wipe one pixel row at a parameterized address
LAD30426Two-byte parameter area: target address for WIP

The spacing of exactly 3 bytes between most entry points suggests each is a simple JP nn (3-byte absolute jump) acting as a jump table. LAD at offset 26 is not itself called; it is a data area poked by the BASIC wipe loop before calling WIP.

Key BASIC Idioms

  • VAL "number" throughout: A standard memory-saving technique — storing numeric literals as short string tokens rather than 5-byte floating-point constants in the BASIC line.
  • Computed GO TO RET: The input dispatcher stores a return address in variable RET and jumps to it with GO TO RET, simulating a computed GOSUB return without a full subroutine stack entry.
  • Uppercase normalization (line 420): IF K$>"Z" THEN LET K$=CHR$ (CODE K$-32) converts any lowercase letter to uppercase, making the key-dispatch case-insensitive without duplicating every IF branch.
  • Debounce at line 410: IF K$=INKEY$ THEN GO TO 410 spins until the key is physically released, preventing a single press from being acted on multiple times in the main loop.
  • RUN as a soft reset: Used liberally (lines 250, 265, 272, 280, 304, 322, 340, 360, 700, 706, 730, 920) to restart the program cleanly rather than managing complex state recovery.

Wipe Routine Detail (Lines 700–740)

The wipe iterates over Spectrum screen memory in its natural band/character-row/pixel-row order. The address of each pixel row is computed as:

A = 16384 + (B*2048) + (S*256) + (R*32)

where B is the band (0–2), S is the pixel row within a character row (0–7), and R is the character row within the band (0–7). This is the standard ZX Spectrum screen layout formula. The low and high bytes of A are then POKEd into LAD and LAD+1 before calling USR WIP, which presumably clears that row. The loop counts down X (the user-supplied number of lines) and exits early when it reaches zero.

Save/Load Architecture

Three distinct data objects are managed:

  • RLE file: 37000 to 37000+L bytes, where L comes from the machine code TLN routine.
  • Screen: 6144 bytes from 16384 (bitmap only; attributes are not saved separately).
  • Program: Saved with SAVE N$ LINE 900 (auto-run at line 900) followed immediately by SAVE N$ CODE 30400,400 to bundle the 400-byte machine code block on the same tape file sequence.

Line 900 is the post-load trampoline that reloads the machine code and clears buffers before restarting, ensuring the code block is always present after a tape load.

Notable Anomalies and Bugs

  • Mixed case variable collision: The menu-toggle variable is set as MENU at line 110 but tested as menu at line 40 — in Sinclair BASIC variable names are case-sensitive for multi-character names, so MENU and menu are distinct variables. The test at line 40 always reads the original menu (initialized to 1 at line 20) and will never reflect the toggled value. Similarly MV vs mv may cause issues.
  • Line 392 verify path: When K$="P", VERIFY N$ is called without CODE, verifying the BASIC portion, but then falls through to VERIFY N$ CODE at line 394 which would try to verify the code block — this is actually correct for the two-part save format, though the fall-through is implicit.
  • Input validation in line 290: Filename length is capped at 10 characters, consistent with tape filename limits, but no check is made for invalid filename characters.
  • CLEAR 65368 at line 125: The QUIT handler clears to near the top of the 48K RAM map and then calls STOP, effectively exiting to a clean BASIC state.

Content

Appears On

Capital Area Timex Sinclair User Group’s Library Tape.
Library tape from the Sinclair Computer Users Society (SINCUS).

Related Products

Related Articles

Introduction to the file format, explanation of where to find and view them. Jack Dohany has written an encoder/decoder program.

Image Gallery

QRL

Source Code

    1 REM QRL by Jack Dohany
    2 REM (415)367-7781
    3 REM 10-86
    4 CLEAR VAL "30399"
    5 REM cassette version
    6 DIM E$(VAL "20"): LET C1=VAL "0": LET C2=VAL "7"
   10 LET STB=VAL "30400": LET BTS=STB+VAL "3": LET ECD=STB+VAL "6": LET DCD=STB+VAL "9": LET CPF=STB+VAL "12": LET INV=STB+VAL "15": LET TLN=STB+VAL "18":: LET WIP=STB+VAL "21": LET LAD=STB+VAL "26"
   20 LET menu=VAL "1": LET MV=VAL "11"
   30 BORDER C1: PAPER C1: INK C2 
   35 RANDOMIZE USR BTS
   40 LET L=USR TLN: IF NOT menu THEN GO TO VAL "100"
   50 GO SUB VAL "500"
  100 LET RET=VAL "110": GO TO VAL "400"
  110 IF K$="M" OR K$=CHR$ VAL "13" THEN LET MENU=NOT MENU: GO TO VAL "35"
  120 IF K$="L" THEN LET C$=" LOAD ": GO TO VAL "200"
  125 IF K$="Q" THEN CLEAR VAL "65368": PRINT AT VAL "21",VAL "10";" OK TO RUN ": STOP 
  130 IF K$="S" THEN LET C$=" SAVE ": GO TO VAL "300"
  135 IF K$="C" THEN RANDOMIZE USR BTS: RANDOMIZE USR CPF: GO TO VAL "40"
  140 IF K$="I" THEN RANDOMIZE USR INV: GO TO VAL "35"
  145 IF K$="E" THEN GO TO VAL "260"
  150 IF K$="D" THEN GO TO VAL "270"
  155 IF k$="W" THEN GO TO VAL "700"
  160 GO TO VAL "100"
  190 FOR I=VAL "0" TO VAL "300": NEXT I: BEEP VAL ".01",VAL "25": RETURN 
  200 INPUT "LOAD FILE OR SCREEN? "; LINE K$: LET RET=VAL "201": GO TO VAL "420"
  201 IF k$="F" THEN LET W$=" RLE FILE ": GO TO VAL "220"
  202 IF k$="S" THEN LET W$=" SCREEN ": GO TO VAL "240"
  203 GO TO VAL "35"
  220 GO SUB VAL "290"
  222 LOAD n$CODE 37000: RUN 
  240 GO SUB VAL "290"
  242 CLS : LOAD n$CODE VAL "16384"
  250 RANDOMIZE USR STB: RUN 
  260 PRINT FLASH 1;AT mv+VAL "7",VAL "1";" Allow 10 seconds "
  261 FOR i=VAL "0" TO VAL "250": NEXT i
  265 RANDOMIZE USR BTS: RANDOMIZE USR ECD: RUN 
  270 CLS : LET GOOD=USR DCD
  271 IF NOT GOOD THEN GO TO VAL "280"
  272 RANDOMIZE USR STB: RUN 
  280 PRINT FLASH VAL "1";AT mv+VAL "7",VAL "1";" INVALID RLE FILE ": FOR i=VAL "0" TO VAL "250": NEXT i: RUN 
  290 INPUT ("";C$;" ";W$;'"Filename: ");N$: IF LEN n$>VAL "10" THEN GO TO VAL "290"
  291 RETURN 
  300 INPUT "SAVE FILE, SCREEN or PROG? "; LINE K$: LET ret=VAL "301": GO TO VAL "420"
  301 IF K$="F" THEN LET W$=" RLE FILE ": GO TO VAL "320"
  302 IF K$="S" THEN LET W$=" SCREEN ": GO TO VAL "340"
  303 IF K$="P" THEN LET W$=" PROGRAM ": GO TO VAL "360"
  304 RUN 
  320 IF NOT L THEN GO TO VAL "280"
  322 GO SUB VAL "290": IF n$="" THEN RUN 
  324 SAVE N$CODE 37000,L: GO TO VAL "380"
  340 GO SUB VAL "290": IF n$="" THEN RUN 
  350 RANDOMIZE USR BTS: SAVE n$CODE VAL "30800",VAL "6144": GO TO VAL "380"
  360 GO SUB VAL "290": IF n$="" THEN RUN 
  364 SAVE N$ LINE VAL "900": BEEP VAL ".01",VAL "10": SAVE N$CODE VAL "30400",VAL "400"
  380 CLS : PRINT AT VAL "20",VAL "0";"VERIFY (Y/N) "; FLASH VAL "1";"?"
  381 IF INKEY$="" THEN GO TO VAL "381"
  382 IF INKEY$="y" OR INKEY$="Y" THEN GO TO VAL "390"
  383 IF INKEY$="n" OR INKEY$="N" THEN RUN 
  384 GO TO VAL "381"
  390 CLS : PRINT AT VAL "20",VAL "0";"REWIND/PLAY TO VERIFY ";N$
  392 IF K$="P" THEN VERIFY N$
  394 VERIFY N$CODE : RUN 
  400 LET K$=INKEY$: IF K$="" THEN GO TO VAL "400"
  405 BEEP VAL ".01",VAL "20"
  410 IF K$=INKEY$ THEN GO TO VAL "410"
  420 IF K$>"Z" THEN LET K$=CHR$ (CODE K$-VAL "32")
  430 GO TO RET
  500 PAPER C2: INK C1: PRINT AT MV,VAL "0";E$'" MENU  DECODE  COPY "'E$'" LOAD  ENCODE  WIPE "'E$'" SAVE  INVERT  QUIT "'E$'"     rle file:";L;TAB VAL "20"'E$'
  505 PLOT VAL "3",VAL "171"-MV*VAL "8": DRAW VAL "152",VAL "0": DRAW VAL "0",-VAL "64": DRAW -VAL "152",VAL "0": DRAW VAL "0",VAL "64"
  510 PAPER C1: INK C2: RETURN 
  700 INPUT "Wipe lines (1-192): "; LINE q$: IF q$="" THEN RUN 
  704 FOR i=VAL "1" TO LEN q$: IF q$(i)<"0" OR q$(i)>"9" THEN RUN 
  706 NEXT I: LET X=VAL Q$: IF X<VAL "1" OR X>VAL "192" THEN RUN 
  710 RANDOMIZE USR BTS: FOR B=VAL "2" TO VAL "0" STEP -VAL "1": FOR R=VAL "7" TO VAL "0" STEP -VAL "1": FOR S=VAL "7" TO VAL "0" STEP -VAL "1": LET A=VAL "16384"+(B*VAL "2048")+(S*VAL "256")+(R*VAL "32")
  720 POKE LAD+VAL "1",INT (A/VAL "256"): POKE LAD,A-VAL "256"*PEEK (LAD+VAL "1"): RANDOMIZE USR WIP
  730 LET X=X-VAL "1": IF X=VAL "0" THEN RANDOMIZE USR STB: GO TO VAL "40"
  740 NEXT S: NEXT R: NEXT B: RUN 
  900 LOAD N$CODE 
  910 RANDOMIZE USR VAL "30741": REM clear buffers
  920 RUN 

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

Scroll to Top