Cassette Directory

Developer(s): Michael Carver
Date: 1986
Type: Program
Platform(s): TS 2068

This program catalogs the contents of a cassette tape by reading file headers directly from memory, displaying the file type (BASIC, Array, or Code), filename, byte length, and load/autostart address for each block found. It uses a machine code routine POKEd into RAM at address 65451 (near the top of a 64K address space) to intercept the tape-loading mechanism, with the header data examined at address 65475. The directory can be printed to a printer via channel 3, saved to tape as a character array, or reloaded for later review. A hex-string-to-machine-code loader at lines 9100–9110 converts two ASCII hex digits at a time into byte values using arithmetic on character codes, with a checksum validation step to catch transcription errors.


Program Analysis

Program Structure

The program is organized around a menu loop at lines 100–130, with distinct functional modules reached via computed GO TO targets. The main tape-reading loop runs from lines 15–80, the printer toggle is at 200–240, the save routine at 300–360, the load routine at 600–690, and the machine code installer at 9000–9110. Lines 9900–9999 handle self-save/verify and cleanup.

LinesFunction
10–80Tape header capture and display loop
100–130Main menu and dispatch
200–240Printer toggle
300–360Save directory to tape
400–420Keypress subroutine (wait-for-release, then wait-for-press)
500–510Tape name/side initialisation
600–690Load and review saved directory
9000–9110Machine code hex-string installer with checksums
9990Tape error handler
9998–9999Self-save/verify sequence

Machine Code Usage

The installer routine at lines 9000–9110 encodes two separate Z80 routines as hex strings in a$. The first (40 bytes, 80 hex chars) is loaded at address 65451; RANDOMIZE USR 65451 at line 9020 executes it immediately as a test. The second routine (24 bytes, 48 hex chars) is also placed at 65451 (overwriting the first), and is the one used at runtime by line 20.

The hex-to-byte conversion in the subroutine at line 9110 uses the expression (CODE a$(x)-(48 AND CODE a$(x)<58)-(55 AND CODE a$(x)>64)) to convert each ASCII hex digit: subtracting 48 for digit characters (‘0’–’9’) and 55 for letter characters (‘A’–’F’), yielding values 0–15. Two such nibbles are combined with *16+.

Checksum validation at line 9100 sums the ASCII codes of all characters in a$. Expected values (4735 and 2887) are hard-coded, guarding against transcription errors when the program is entered by hand.

At line 9040, a secondary block of 8 bytes is POKEd into address 65286 using offsets read from a DATA statement, with a separate data checksum check (expected sum 133). The byte 254 is POKEd as a sentinel before the offset-addressed 255 bytes are placed.

Header Parsing at Runtime

After RANDOMIZE USR 65451 triggers the tape-reading machine code, line 20 reads the captured header from address ix=65475. The type byte at ix+1 is used to select the label string: 0=BASIC, 1 or 2=Array, 3=Code. The filename occupies 10 bytes at ix+2 to ix+11. Byte length is taken from a two-byte little-endian word at ix+12/ix+13, and the load/autostart parameter from ix+14/ix+15.

For array types (t=1 or t=2), line 60 reconstructs the variable name: it reads the byte at ix+15, subtracts 32 for a numeric array (t=1) or 96 for a string array (t=2), and appends $ only for string arrays (t=2), along with ().

Display Formatting with z$

The string z$ is constructed at line 10 as CHR$ 19+CHR$ 1+CHR$ 19+CHR$ 0, which represents ink colour control sequences (bright ink on, bright ink off). Slicing z$( TO 2) gives the “bright on” prefix and z$(3 TO ) gives the “bright off” suffix, so field labels are displayed in bright ink while values are in normal ink.

Key BASIC Idioms

  • Conditional string concatenation using ("string" AND condition) — produces the string if true, empty string if false.
  • Computed GO TO at line 130 using arithmetic on Boolean results of k$ comparisons to select the branch target.
  • The keypress subroutine (lines 400–420) first waits for any currently held key to be released, then waits for a new keypress — a standard debounce idiom.
  • POKE 23692,0 at line 70 resets the scroll counter (system variable SCRCT) to prevent the “scroll?” prompt interrupting output.
  • INPUT INKEY$ at lines 360 and 600 clears any key already in the buffer before waiting for tape operations.
  • ON ERR GO TO (rendered as { in the source) provides structured error handling throughout; ON ERR RESET (© ) at line 120 clears the error handler within the menu.

Directory Storage

Each parsed header entry is accumulated into b$ (line 70), separated by double carriage returns. The tape name and side, set in the subroutine at lines 500–510, are stored at the start of b$ followed by CHR$ 255 as a delimiter; k holds the length of this prefix. On save (line 330), b$ is copied into a DIM’d character array c$() matching its current length before being saved with SAVE … DATA c$(). On load, the delimiter is located by scanning for CHR$ 255 (lines 610–630) to recover k.

Self-Save Sequence

Line 9999 saves the BASIC program itself with SAVE "directory" LINE 9998 so it autoruns to line 9998 on load, then saves the machine code block at address 65253 (length 222 bytes) as a separate CODE file. Line 9998 then loads that CODE block back and jumps to the main menu. Line 9900 uses DELETE 9000,9988 to remove the installer lines before the self-save, keeping the runtime image clean.

Bugs and Anomalies

  • At line 130, the arithmetic uses both addition and subtraction of line-number offsets in a single expression to select the menu branch. The expression relies on exactly one k$ comparison being true; if none match, the result is GO TO 120, repeating the keypress wait — a safe default.
  • The GO TO 9988 at line 9080 targets a non-existent line; execution will fall through to line 9900, which clears and deletes the installer lines — an intentional technique.
  • Line 9989 (GO TO 9999) appears to be reachable only if line 9900’s DELETE somehow did not reach line 9989 itself; in practice it serves as a forward jump past the error handler at 9990 into the self-save at 9999.

Content

Appears On

One of a series of library tapes. Programs on these tapes were renamed to a number series. This tape contained

Related Products

Related Articles

Related Content

Image Gallery

Cassette Directory

Source Code

    1 REM ***********************           Cassette Directory            1986 Michael E. Carver          ***********************
   10 ON ERR GO TO 100: CLS : GO SUB 500: LET z$=CHR$ 19+CHR$ 1+CHR$ 19+CHR$ 0: CLS : IF print THEN PRINT #3;"Directory of ";b$( TO k)''
   15 CLS : ON ERR GO TO 100: PRINT AT 2,11;"BREAK/CAPS will return to MENU";#1;"Start tape, then press any key.": GO SUB 400: CLS 
   20 RANDOMIZE USR 65451: LET ix=65475: LET t=PEEK (ix+1): LET a$=z$( TO 2)+("BASIC" AND NOT t)+("Array" AND (t=1 OR t=2))+("Code" AND t=3)+":"+z$(3 TO )+" "
   30 FOR n=ix+2 TO ix+11: LET a$=a$+CHR$ (PEEK n): NEXT n: LET a$=a$+" "+z$( TO 2)+"Bytes:"+z$(3 TO )+" "+STR$ (PEEK (ix+12)+256*PEEK (ix+13))
   40 LET s=(PEEK (ix+14)+256*PEEK (ix+15)): LET a$=a$+CHR$ 13+"     "+(z$( TO 2)+"Loads at"+z$(3 TO )+" "+STR$ s AND t=3)+(z$( TO 2)+("Autostart at"+z$(3 TO )+" "+STR$ s AND (NOT t AND s<=9999)))
   50 IF NOT t AND s>9999 THEN LET a$=a$+z$( TO 2)+"No Autostart"+z$(3 TO )
   60 IF (t=1 OR t=2) THEN LET a$=a$+z$( TO 2)+"Variable"+z$(3 TO )+" "+CHR$ (PEEK (ix+15)-(32 AND t=1)-(96 AND t=2))+("$" AND t=2)+"()"
   70 POKE 23692,0: PRINT a$'': LET b$=b$+a$+CHR$ 13+CHR$ 13: IF print THEN PRINT #3;a$''
   80 GO TO 20
  100 CLS : PRINT AT 2,6;"CASSETTE DIRECTORY"
  110 PRINT AT 8,0;"CREATE DIRECTORY...............1"''"PRINTER SWITCH.................2"''"SAVE TO TAPE...................3"''"CONTINUE DIRECTORY.............4"''"LOAD FROM TAPE.................5"''"REVIEW DIRECTORY...............6"
  120 GO SUB 400: ON ERR RESET 
  130 GO TO 120-(110 AND k$="1")+(80 AND k$="2")+(180 AND k$="3")-(105 AND k$="4")+(480 AND k$="5")+(490 AND k$="6")
  200 ON ERR GO TO 100: CLS : PRINT AT 1,8;"PRINTER SWITCH"
  210 PRINT AT 5,10;"PRINTER "; FLASH 1;;("ON " AND print);("OFF " AND NOT print); FLASH 0;AT 20,0;"Press ""P"" to toggle switch";TAB 6;"""M"" for Menu"
  220 GO SUB 400
  230 IF k$="p" OR k$="P" THEN LET print=NOT print: GO TO 210
  240 GO TO 220-(120 AND (k$="m" OR k$="M"))
  300 ON ERR GO TO 100: CLS : PRINT ''"Ready to save Directory: "
  310 PRINT "     ";b$( TO k);'"to tape."
  320 INPUT "Save as? ";n$: IF LEN n$>10 THEN LET n$=n$( TO 10)
  330 DIM c$(LEN b$): LET c$=b$: PRINT ''"Saving """;n$;"""": SAVE n$ DATA c$()
  340 PRINT #1;"Verify (y or n)?": GO SUB 400
  350 IF k$="n" OR k$="N" THEN GO TO 100
  360 INPUT INKEY$: PRINT ''"verifying """;n$;"""";''"Rewind tape";#1;"Start tape, then press any key.": GO SUB 400: INPUT INKEY$: ON ERR GO TO 9990: VERIFY n$ DATA c$(): GO TO 100
  400 IF INKEY$<>"" THEN GO TO 400
  410 IF INKEY$="" THEN GO TO 410
  420 LET k$=INKEY$: RETURN 
  500 INPUT "Tape name? ";k$: LET a$=k$: INPUT "Side? ";k$: LET a$=a$+" Side "+k$: LET b$=a$+CHR$ 255: LET k=LEN b$-1
  510 RETURN 
  600 POKE 23692,23: CLS : ON ERR GO TO 100: INPUT "Load name? ";n$: PRINT ''"Loading """;n$;"""";#1;"Start tape, then press any key.": GO SUB 400: INPUT INKEY$: IF LEN n$>10 THEN LET n$=n$( TO 10)
  605 ON ERR GO TO 9990: LOAD n$ DATA c$(): LET b$=c$
  610 ON ERR GO TO 100: CLS : FOR j=1 TO LEN b$: IF b$(j)=CHR$ 255 THEN GO TO 630
  620 NEXT j
  630 LET k=j-1
  640 IF print THEN PRINT #3;"Directory of ";b$( TO k);'';b$(k+2 TO )
  650 PRINT "Directory of ";b$( TO k);'';b$(k+2 TO )
  660 PRINT #1;"Press ""M"" for MENU"
  670 GO SUB 400
  680 IF k$<>"M" AND k$<>"m" THEN GO TO 670
  690 GO TO 100
 9000 CLEAR 65252: LET a$="F33E01D3F4DBFFCBFFD3FF21E50011E58001C600EDB0AFD3FFD3F4FB21E58011E5FE01C600EDB0C9"
 9010 GO SUB 9100: IF LEN a$<>80 OR check<>4735 THEN PRINT FLASH 1;"Error in a$ Line 9000 -----      Please Correct": STOP 
 9020 LET address=65451: GO SUB 9110: RANDOMIZE USR 65451
 9030 DATA 14,15,7,15,10,47,11,14: RESTORE 9030: LET data=0: FOR x=1 TO 8: READ y: LET data=data+y: NEXT x: IF data<>133 THEN PRINT FLASH 1;"Error in Line 9030 -----         Please Correct": STOP 
 9040 RESTORE 9030: LET address=65286: POKE address,254: FOR x=1 TO 8: READ y: LET address=address+y: POKE address,255: NEXT x
 9050 LET a$="AF3700DD21C3FF11150008CDFFFE2AC3FF7DFE00C8C3ABFF"
 9060 GO SUB 9100: IF LEN a$<>48 OR check<>2887 THEN PRINT FLASH 1;"Error in a$ Line 9040 -----      Please Correct": STOP 
 9070 LET address=65451: GO SUB 9110
 9080 GO TO 9988
 9100 LET check=0: FOR x=1 TO LEN a$: LET check=check+CODE a$(x): NEXT x: RETURN 
 9110 FOR x=1 TO LEN a$-1 STEP 2: POKE address+INT ((x-1)/2),(CODE a$(x)-(48 AND CODE a$(x)<58)-(55 AND CODE a$(x)>64))*16+CODE a$(x+1)-(48 AND CODE a$(x+1)<58)-(55 AND CODE a$(x+1)>64): NEXT x: RETURN 
 9900 CLEAR : DELETE 9000,9988
 9989 GO TO 9999
 9990 ON ERR GO TO 100: CLS : PRINT FLASH 1;AT 5,8;"Tape Loading Error"; FLASH 0;'''TAB 8;"Please Attempt Again";#1;"Press any key for Menu": GO SUB 400: GO TO 100
 9998 CLEAR 65252: LOAD "directory"CODE 65253: LET print=0: GO TO 100
 9999 CLS : PRINT "Saving directory": SAVE "directory" LINE 9998: SAVE "directory"CODE 65253,222: CLS : PRINT "Ready to Verify -- Please Rewind Tape -- Start Tape and Press anyKey.": GO SUB 400: CLEAR : VERIFY "directory": VERIFY "directory"CODE : LET print=0: GO TO 100

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

Scroll to Top