--- title: "QRL" id: 53844 type: "computer_media" slug: "qrlfor2068" url: "http://localhost/computer_media/qrlfor2068/" markdown_url: "http://localhost/computer_media/qrlfor2068.md" published_at: "2024-05-13T19:02:05+00:00" modified_at: "2026-03-30T21:44:51+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/05/13-rle.png" excerpt: "A compact RLE graphics manager with machine code encode, decode, invert, and wipe routines lets you store, load, and manipulate compressed screen images from cassette." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Jack Dohany" slug: "jack-dohany" taxonomy: "indiv" url: "http://localhost/indiv/jack-dohany/" genre: - name: "Graphics" slug: "graphics" taxonomy: "genre" url: "http://localhost/type/graphics/" media_contents: - id: 55361 title: "SINCUS Exchange Tape 102 – Utilities & Business" type: "computer_media" url: "http://localhost/computer_media/sincus-exchange-tape-102-utilities-business/" - id: 64507 title: "CATS Library Tape 9" type: "computer_media" url: "http://localhost/computer_media/cats-library-tape-9/" media_type: "Program" programmers: - name: "Jack Dohany" slug: "jack-dohany" taxonomy: "indiv" url: "http://localhost/indiv/jack-dohany/" mediadate: "1986" images: - url: "http://localhost/wp-content/uploads/2024/05/13-rle.png" article_media: - id: 37977 title: "RLE Graphics" type: "article" url: "http://localhost/article/rle-graphics-2/" related_content: - id: 54670 title: "QRL Quick RLE graphics encoder decoder for the 2068" type: "document" url: "http://localhost/document/qrl-quick-rle-graphics-encoder-decoder-for-the-2068/" media_type_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`: | Variable | Address | Purpose | | --- | --- | --- | | `STB` | 30400 | Screen-to-buffer blit / display restore | | `BTS` | 30403 | Buffer-to-screen blit | | `ECD` | 30406 | Encode screen to RLE | | `DCD` | 30409 | Decode RLE to screen; returns success flag | | `CPF` | 30412 | Copy/flip routine (used on COPY key) | | `INV` | 30415 | Invert image | | `TLN` | 30418 | Return total line count / RLE data length | | `WIP` | 30421 | Wipe one pixel row at a parameterized address | | `LAD` | 30426 | Two-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. ## 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 XVAL "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 ```