--- title: "Tape Lister – Centronics" id: 56130 type: "computer_media" slug: "tape-lister-centronics" url: "http://localhost/computer_media/tape-lister-centronics/" markdown_url: "http://localhost/computer_media/tape-lister-centronics.md" published_at: "2024-07-19T02:08:34+00:00" modified_at: "2026-03-30T21:45:54+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/07/SCR-20240718-tbip.png" excerpt: "This tape cataloging utility reads header blocks directly from cassette, decodes all four file types, and stores up to 128 entries for printing or saving." 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/" genre: - name: "Tape Directory" slug: "tape-directory" taxonomy: "genre" url: "http://localhost/type/tape-directory/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/TapeListerCent%20%28198x%299TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/07/SCR-20240718-tbip.png" media_type_tags: "Tape Directory" --- This program reads and catalogs tape header information from cassette tapes, storing up to 128 entries in a string array and displaying each header as it is read. A 45-byte machine code routine is POKEd into RAM at address 61952 and called via RANDOMIZE USR to interface directly with the tape hardware; a working address at offset 45 (wh=61997) serves as a shared communication buffer between the Z80 routine and BASIC. The program decodes all four standard file types — PROG (with autostart LINE detection), numeric arrays, string arrays, and CODE blocks — formatting each entry with name, type, length, and address information. Printer support covers both the TS 2040 and any Centronics-compatible printer via a Z80 PIO chip configured with OUT commands and a secondary machine code block at 61594. The collected headers can be saved back to tape as a named DATA array, with the entry count embedded in the filename. *** ## Program Analysis ### Program Structure The program is divided into several functional regions with non-contiguous line numbering reflecting distinct phases: - **Lines 1–3, 200:** Startup banner, description display, and initial STOP prompting the user to stop the tape recorder. - **Lines 399–422:** Memory reservation (`CLEAR 59903`), printer setup subroutine supporting both T-S 2040 and Centronics/PIO configurations. - **Lines 450–452:** Graphics copy/print helper using LPRINT escape sequences and a USR call (likely for a screen dump). - **Lines 3995–4010:** Main tape reading loop — machine code installation, array initialization, and the header-decode/display/log cycle. - **Lines 4500–4600:** STOP and bulk display of all collected entries. - **Lines 9990–9999:** Save section — pads the entry count into the filename, saves the array, and re-saves the BASIC program itself with `LINE 1` autostart. ### Machine Code Usage Two separate machine code blocks are installed by BASIC: 1. **Centronics PIO printer driver (lines 417–418):** 26 bytes POKEd into address 61594–61619. The DATA includes Z80 opcodes for IN/OUT operations using the configurable port addresses `C1`–`C6`, with a busy-wait loop and strobe generation. 2. **Tape header reader (lines 3995–3997):** 45 bytes POKEd at `loadat=61952`. The routine uses `IN A,(255)` (EAR port) and writes results into a 17-byte buffer at `wh=61997` (loadat+45). BASIC POKEs a flag value of 4 into `wh` before each call, and reads it back after: if `wt>3`, no valid header was found and the loop retries after a pause. The tape routine at 61952 communicates via a shared memory area beginning at `wh`. The layout mirrors the standard tape header structure: type byte at offset 0, filename at offsets 1–10, length at offsets 11–12, parameter 1 at offsets 13–14, parameter 2 at offsets 15–16. ### Header Decoding After a successful read, line 4000 assembles the 10-character filename from `wh+1` to `wh+10`, then dispatches on the type byte `wt` via a computed `GO TO 4001+wt`: | wt | Line | Type | Format | | --- | --- | --- | --- | | 0 | 4001 | PROG | name + PROG, vars length, autostart info | | 1 | 4002 | Numeric array | name + letter + `()` + length | | 2 | 4003 | String array | name + letter + `$()` + length | | 3 | 4004 | CODE | name + CODE, length, load address | For PROG files, autostart detection compares `PEEK (wh+14)` to 128: a value of 128 means no autostart LINE, otherwise the LINE number is reconstructed from `wa`. The result is written into columns 36 onward of the 64-character array row using a string slice assignment with a boolean AND idiom. ### Key BASIC Idioms - **Boolean string selection:**`("Autostart LINE "+STR$ wa) AND (PEEK (wh+14)<>128)` evaluates to the string if the condition is true, or an empty string if false. This is a standard Sinclair-dialect trick exploiting the fact that AND with a zero numeric operand yields 0 (empty string). - **Computed GO TO:**`GO TO 4001+wt` dispatches to one of four handler lines based on the file type — a clean jump table using arithmetic on line numbers. - **Left-padded string counter (lines 9990–9991):** A `GO TO` loop pads `w$` to 3 characters with leading spaces before embedding it in the filename at `z$(1,12 TO 14)`. - **Printer detection at line 4005:**`LET w=PEEK 26704=PEEK 26694` stores the boolean result of a comparison directly into variable `w` — used to check whether the T-S 2040 printer ROM vector is in effect. - **Self-saving with LINE autostart (line 9998):**`SAVE "TapeLister" LINE 1` saves the BASIC program so it auto-runs from line 1 on load. The preceding `DIM z$(1,1)` minimizes the variable area included in the save. ### Printer Configuration The printer setup subroutine (lines 410–422) supports two printer types. For the T-S 2040, line 414 copies two bytes from the ROM’s default LPRINT vector addresses (26693–26694) into the system’s current vector pointers (26703–26704). For Centronics printers via a Z80 PIO, lines 418–422 install the machine code driver and initialize the PIO channels with `OUT` instructions using configurable port addresses, then redirect the LPRINT vector to the installed routine at address `240*256+154 = 61594`. The display at line 415 shows current port values and invites the user to modify variables `C1`–`C6` and re-run line 415 before continuing — an interactive configuration loop gated by `STOP` at line 416. ### Array Storage `DIM z$(129,64)` allocates a 129-row by 64-column string array. Row 1 is used as a header record (`"TapeHdrs /128"`), with the entry count eventually written into columns 12–14. Rows 2–129 hold individual tape header descriptions. The counter `ww` starts at 2 and increments with each successfully decoded header. The final save at line 9995 uses `SAVE z$(1, TO 10) DATA z$()`, which takes the first 10 characters of row 1 as the filename — matching the `"TapeHdrs "` prefix — and saves the entire array as a DATA file. ### Notable Anomalies - Line 4010 references `GO TO 4006` indirectly via a forward branch from line 3999 (`IF wt>3 THEN GO TO 4006`), but line 4006 does not exist in the listing. This is a well-known technique: on this platform, a GO TO targeting a non-existent line falls through to the next available line, which would be 4010 — the retry/pause loop. This is intentional. - Lines 450–452 reference a variable `LDat` (for a screen dump USR call) that is never assigned in the visible listing, suggesting these lines are only reachable from a separate part of the program not shown, or require manual setup. - The DATA at line 417 contains the literal token identifiers `C3`, `C1`, and `C5` as symbolic names embedded in the DATA list. At runtime, BASIC reads these as the current numeric values of variables `C3`, `C1`, and `C5`, so the machine code is patched with the user-configured port addresses — a neat self-modifying code pattern mediated through BASIC DATA. ## Source Code ``` 1 CLS : PRINT FLASH 1;AT 3,8;" STOP RECORDER " 2 PRINT ''" This program accesses tape header information. It creates a file of up to 128 entries as well as displaying the results."''" It is configured for printing to any CENTRONICS printer." 3 PAUSE 600 200 STOP 399 CLEAR 59903 410 REM Printer Setup 411 LET C1=130: LET C2=131: LET C3=128: LET C4=129: LET C5=16: LET C6=64 412 CLS : PRINT ,," Please select PRINTER."''" 1 T-S 2040"''" 2 CENTRONICS w/PIO, IM1" 413 INPUT "Which? ";Printer: IF Printer=2 THEN GO TO 415 414 POKE 26703,PEEK 26693: POKE 26704,PEEK 26694: RETURN 415 PRINT AT 3,1;" PRINTER ADDRESSES "''" Printer: c1","Command: c2"'" Status: c3","Command: c4"'" BUSY bit: c5","STB Bit: c6"''" PRESENT VALUES "''" c1=";c1,"c2=";c2'" c3=";c3,"c4=";c4'" c5=";c5,"c6=";c6 416 PRINT '" Input new values as required and GO TO 415, else CONTINUE.": STOP 417 RESTORE 417: DATA 8,219,C3,230,C5,32,12,8,211,C1,175,243,211,C3,47,211,C3,251,201,205,9,32,56,-23,207,12 418 FOR N=61594 TO 61619: READ Byte: POKE N,Byte: NEXT N 420 REM init PIO 421 OUT C2,255: OUT C2,0: OUT C2,7: OUT C4,255: OUT C4,255-C6: OUT C4,7: OUT C3,64 422 POKE 26703,154: POKE 26704,240: GO TO 999 450 LPRINT ("\l/" AND Zoom);"\lG";STR$ Margin;";32;22;";STR$ Aspect;"\lZ";: RANDOMIZE USR LDat: RETURN : REM COPY 451 LET Zoom=0: LET Margin=40: LET aspect=3: GO TO 450 452 LET Zoom=1: LET Margin=40: LET aspect=2: GO TO 450 3995 DATA 62,0,55,221,33,45,242,17,17,0,243,245,219,255,203,255,211,255,219,244,50,44,242,62,1,211,244,241,205,252,0,58,44,242,211,244,219,255,203,191,211,255,251,201,0 3996 LET loadat=61952: LET wh=loadat+45: REM wh=61997 3997 RESTORE 3995: FOR n=loadat TO loadat+44: READ wc: POKE n,wc: NEXT n: DIM z$(129,64): LET z$(1)="TapeHdrs /128": LET ww=2: LET w$="": CLS : PRINT AT 10,6;" START RECORDER ": PRINT AT 0,0; 3998 POKE wh,4: RANDOMIZE USR loadat: LET wt=PEEK wh: IF wt>3 THEN GO TO 4010 3999 LET wl=PEEK (wh+11)+256*PEEK (wh+12): LET wc=PEEK (wh+14): LET wa=PEEK (wh+13)+256*wc: LET wv=PEEK (wh+15)+256*PEEK (wh+16): LET wt=PEEK wh: IF wt>3 THEN GO TO 4006 4000 LET w$="": FOR n=wh+1 TO wh+10: LET w$=w$+CHR$ PEEK n: NEXT n: GO TO 4001+wt 4001 LET Z$(WW)=w$+" PROG "+STR$ wv+", VARS "+STR$ (wl-wv): LET z$(ww,36 TO )=(("Autostart LINE "+STR$ wa) AND (PEEK (wh+14)<>128))+("No Autostart" AND (PEEK (wh+14)=128)): GO TO 4005 4002 LET Z$(WW)=w$+" "+CHR$ (wc-32)+"(), "+STR$ wl: GO TO 4005 4003 LET Z$(WW)=w$+" "+CHR$ (wc-96)+"$(), "+STR$ wl: GO TO 4005 4004 LET Z$(WW)=w$+" CODE "+STR$ wl+" from "+STR$ wa 4005 PRINT AT 0,0;z$(ww): LPRINT z$(ww);CHR$ 10;: LET ww=ww+1: LET w=PEEK 26704=PEEK 26694 4010 PAUSE 300: GO TO 3998 4500 STOP 4600 FOR n=1 TO ww: PRINT z$(n): NEXT n: STOP 9990 LET w$=STR$ (ww-1) 9991 IF LEN w$<3 THEN LET w$=" "+w$: GO TO 9991 9994 LET z$(1,12 TO 14)=w$ 9995 SAVE z$(1, TO 10) DATA z$() 9996 STOP 9998 DIM z$(1,1): SAVE "TapeLister" LINE 1 9999 GO TO 9998 1 CLS : PRINT FLASH 1;AT 3,8;" STOP RECORDER " 2 PRINT ''" This program accesses tape header information. It creates a file of up to 128 entries as well as displaying the results."''" It is configured for printing to any CENTRONICS printer." 3 PAUSE 600 200 STOP 399 CLEAR 59903 410 REM Printer Setup 411 LET C1=130: LET C2=131: LET C3=128: LET C4=129: LET C5=16: LET C6=64 412 CLS : PRINT ,," Please select PRINTER."''" 1 T-S 2040"''" 2 CENTRONICS w/PIO, IM1" 413 INPUT "Which? ";Printer: IF Printer=2 THEN GO TO 415 414 POKE 26703,PEEK 26693: POKE 26704,PEEK 26694: RETURN 415 PRINT AT 3,1;" PRINTER ADDRESSES "''" Printer: c1","Command: c2"'" Status: c3","Command: c4"'" BUSY bit: c5","STB Bit: c6"''" PRESENT VALUES "''" c1=";c1,"c2=";c2'" c3=";c3,"c4=";c4'" c5=";c5,"c6=";c6 416 PRINT '" Input new values as required and GO TO 415, else CONTINUE.": STOP 417 RESTORE 417: DATA 8,219,C3,230,C5,32,12,8,211,C1,175,243,211,C3,47,211,C3,251,201,205,9,32,56,-23,207,12 418 FOR N=61594 TO 61619: READ Byte: POKE N,Byte: NEXT N 420 REM init PIO 421 OUT C2,255: OUT C2,0: OUT C2,7: OUT C4,255: OUT C4,255-C6: OUT C4,7: OUT C3,64 422 POKE 26703,154: POKE 26704,240: GO TO 999 450 LPRINT ("\l/" AND Zoom);"\lG";STR$ Margin;";32;22;";STR$ Aspect;"\lZ";: RANDOMIZE USR LDat: RETURN : REM COPY 451 LET Zoom=0: LET Margin=40: LET aspect=3: GO TO 450 452 LET Zoom=1: LET Margin=40: LET aspect=2: GO TO 450 3995 DATA 62,0,55,221,33,45,242,17,17,0,243,245,219,255,203,255,211,255,219,244,50,44,242,62,1,211,244,241,205,252,0,58,44,242,211,244,219,255,203,191,211,255,251,201,0 3996 LET loadat=61952: LET wh=loadat+45: REM wh=61997 3997 RESTORE 3995: FOR n=loadat TO loadat+44: READ wc: POKE n,wc: NEXT n: DIM z$(129,64): LET z$(1)="TapeHdrs /128": LET ww=2: LET w$="": CLS : PRINT AT 10,6;" START RECORDER ": PRINT AT 0,0; 3998 POKE wh,4: RANDOMIZE USR loadat: LET wt=PEEK wh: IF wt>3 THEN GO TO 4010 3999 LET wl=PEEK (wh+11)+256*PEEK (wh+12): LET wc=PEEK (wh+14): LET wa=PEEK (wh+13)+256*wc: LET wv=PEEK (wh+15)+256*PEEK (wh+16): LET wt=PEEK wh: IF wt>3 THEN GO TO 4006 4000 LET w$="": FOR n=wh+1 TO wh+10: LET w$=w$+CHR$ PEEK n: NEXT n: GO TO 4001+wt 4001 LET Z$(WW)=w$+" PROG "+STR$ wv+", VARS "+STR$ (wl-wv): LET z$(ww,36 TO )=(("Autostart LINE "+STR$ wa) AND (PEEK (wh+14)<>128))+("No Autostart" AND (PEEK (wh+14)=128)): GO TO 4005 4002 LET Z$(WW)=w$+" "+CHR$ (wc-32)+"(), "+STR$ wl: GO TO 4005 4003 LET Z$(WW)=w$+" "+CHR$ (wc-96)+"$(), "+STR$ wl: GO TO 4005 4004 LET Z$(WW)=w$+" CODE "+STR$ wl+" from "+STR$ wa 4005 PRINT AT 0,0;z$(ww): LPRINT z$(ww);CHR$ 10;: LET ww=ww+1: LET w=PEEK 26704=PEEK 26694 4010 PAUSE 300: GO TO 3998 4500 STOP 4600 FOR n=1 TO ww: PRINT z$(n): NEXT n: STOP 9990 LET w$=STR$ (ww-1) 9991 IF LEN w$<3 THEN LET w$=" "+w$: GO TO 9991 9994 LET z$(1,12 TO 14)=w$ 9995 SAVE z$(1, TO 10) DATA z$() 9996 STOP 9998 DIM z$(1,1): SAVE "TapeLister" LINE 1 9999 GO TO 9998 ```