Tape Examiner

Developer(s): Algis Gedris
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Tape

Tape Examiner reads cassette tape headers and displays the metadata they contain, including file type, filename, data length, start address, and autostart line number. It uses a machine code routine POKEd into RAM at address 64000–64079, loaded from a decimal-encoded string, which interfaces with the Extended ROM’s R_TAPE module to capture incoming headers without loading the full file. The program uses ON ERR to handle tape errors gracefully and loop back for the next header. A DEF FN helper reads 16-bit little-endian values from the header buffer at address 64080. Special cases handle number arrays, character arrays, program files, and screen images (detected by checking for a 6912-byte block starting at address 16384).


Program Structure

The program is organized into several logical sections:

  1. Lines 10–80: Introduction and user prompt — explains the program’s function and asks whether to display results on screen or printer.
  2. Lines 90–130: Input handling — waits for keypress "1" (screen) or "2" (printer), opening stream #2 to the printer if needed.
  3. Lines 140–290: Main examination loop — calls the machine code routine, reads the header buffer, and dispatches to type-specific subroutines.
  4. Lines 300–330: Setup subroutine — POKEs the machine code into RAM and returns.
  5. Lines 1000–1340: File-type subroutines, one per type: program (1000), number array (1100), character array (1200), bytes/code (1300).
  6. Line 9998: SAVE line for storing the program itself.

Machine Code Loader

The subroutine at line 300 loads 80 bytes of machine code into addresses 64000–64079 by decoding a long decimal string stored in a$ at line 320. Each byte is encoded as a zero-padded three-digit decimal value. The loop at line 330 uses VAL a$( TO PI) — which effectively evaluates a$(1 TO 3) since PI truncates to 3 — to extract three characters at a time, then advances a$ by slicing from position 4 onward. This is a compact idiom for sequential three-digit parsing without a separate index variable.

The machine code entry point is at address 64064, called via RANDOMIZE USR 64064 at line 150. The routine interfaces with the Extended ROM’s R_TAPE module to capture an incoming tape header and deposit it into a 17-byte buffer starting at address 64080 (b=64080). The header is not fully loaded — only the header block is captured, leaving tape data on the tape.

Header Buffer and DEF FN

Line 140 sets b=64080 as the base address of the header buffer and defines a function FN a(x) to read a 16-bit little-endian word: PEEK (b+x) + 256 * PEEK (b+x+1). This is used throughout the type-specific subroutines to extract length, start address, and autostart line fields from the standard tape header format.

File Type Dispatch

Line 160 reads the type byte at PEEK b (offset 0 of the header). Line 170 skips to the next header if the type byte is greater than 3, guarding against corrupt or non-standard headers. Line 210 dispatches to the appropriate subroutine using GO SUB 1000+100*c, mapping type values 0–3 to lines 1000, 1100, 1200, and 1300 respectively — a clean computed GO SUB pattern.

File Type Subroutines

LineType byteDescription
10000Program: prints total length, program length, and autostart line (or “Load only” if autostart > 9999)
11001Number array: prints array length and original array name
12002Character array: same as number array but appends $ to name
13003Bytes/code: prints start address and length; detects screen images by checking for length 6912 and start 16384

Array Name Decoding

Line 1240 recovers the original array variable name from the header byte at offset 14 (d = PEEK (b+14)). The expression CHR$ VAL "(64+32*(d/32 - INT (d/32)))" extracts the lower 5 bits of d and adds 64, converting the encoded name byte back to a letter. The use of VAL on a string expression is an unusual but valid technique here to avoid a direct numeric expression that might cause issues, though it is slightly redundant.

Error Handling and Loop Control

Line 150 sets ON ERR GO TO 250 before each call to the machine code routine, so tape errors (such as the user pressing BREAK or a read failure) divert to the cleanup block at line 250. After printing header details, line 230 POKEs 255 into the type byte at b to invalidate the previous header, then line 240 loops back to line 150 to wait for the next header. Lines 250–290 handle shutdown: resetting the error handler with ON ERR RESET, closing stream #2, and stopping.

Notable Techniques

  • PAUSE NOT PI at line 80 evaluates NOT PI as 0 (since PI is non-zero and thus logically true, NOT gives 0), producing PAUSE 0 — an indefinite pause waiting for a keypress, a common idiom.
  • The computed GO SUB 1000+100*c at line 210 avoids a chain of IF statements for type dispatch.
  • CLEAR 63999 at line 20 sets RAMTOP just below the machine code area, protecting it from being overwritten by BASIC.
  • The three-digit decimal encoding with VAL a$( TO PI) is a compact, self-consuming string parsing technique that avoids a separate pointer variable.

Potential Anomalies

  • Line 120 (GO TO 80) is unreachable: lines 90–110 handle all cases where INKEY$ is in range, and line 90 loops until a valid key is pressed. The branch at line 120 can never be reached in normal execution.
  • The OPEN #2,"p" at line 110 redirects output to the printer; however, the PRINT statements in the main loop and subroutines do not explicitly address stream #2, so this relies on the default stream being redirected, which may not behave as intended on all system configurations.

Image Gallery

Source Code

   10 REM This tape was donated by theTORONTO TIMEX SINCLAIR USER'SGroupMODIFIED & COPIED BY ALGIS E.GEDRIS, 12/20/86
   20 CLEAR 63999:GO SUB 300
   30 CLS 
   40 PRINT "Tape Examiner. When you start   this program, it will switch to R_TAPE module in the EXtended   ROM and wait for an incoming    header."
   50 PRINT '"It will print out the details   contained in the header:"'"File type; file name; bytes of  data; special info; program","byte length."
   60 PRINT '"It will also identify a screen  image."
   70 PRINT ''"Prepare a tape in the recorder  and press 1 for screen or","2 for printer display."''"While the program is running    you may press BREAK to Stop"
   80 PAUSE NOT PI
   90 IF INKEY$<"1" OR INKEY$>"2" THEN GO TO 90
  100 IF INKEY$="1" THEN GO TO 130
  110 IF INKEY$="2" THEN OPEN #2,"p":GO TO 130
  120 GO TO 80
  130 CLS 
  140 LET b=64080:DEF FN a(x)= PEEK (b+x)+256* PEEK (b+x+1)
  150 ON ERR GO TO 250:RANDOMIZE USR 64064
  160 LET c= PEEK b
  170 IF c>3 THEN GO TO 150
  180 PRINT "Filename: ";
  190 FOR a=b+1 TO b+10:PRINT CHR$ PEEK a;:NEXT a
  200 PRINT :PRINT TAB 4;"type: ";
  210 GO SUB 1000+100*c
  220 PRINT :PRINT 
  230 POKE b,255
  240 GO TO 150
  250 ON ERR RESET 
  260 CLOSE #2
  270 ON ERR GO TO 250
  280 ON ERR CONTINUE 
  290 STOP 
  300 REM TAPE EXAM
  310 PRINT ''"Setting code"
  320 LET a$="033252000205045250024023033104000205045250024015033142014245205045250241254128032003050194092058044250211244219255203191211255251201000243245219255203255211255219244050044250062001211244241233175055221033080250205000250201000000000000000000"
  330 FOR f=64000 TO 64079:POKE f, VAL a$( TO PI):LET a$=a$(4 TO ):NEXT f:RETURN 
 1000 PRINT "program"
 1010 PRINT "Total length: "; FN a(11);" bytes"
 1020 PRINT "Program length: "; FN a(15);" bytes"
 1030 IF FN a(13)>9999 THEN PRINT "Load only":RETURN 
 1040 PRINT "Runs from line "; FN a(13)
 1050 RETURN 
 1100 PRINT "number array"
 1110 LET a$="":GO TO 1220
 1200 PRINT "character array"
 1210 LET a$="$"
 1220 PRINT "Array length: "; FN a(11);" bytes"
 1230 LET d= PEEK (b+14)
 1240 PRINT "Original array name: "; CHR$ VAL "(64+32*(d/32- INT (d/32)))";a$
 1250 RETURN 
 1300 IF FN a(11)=6912 AND FN a(13)=16384 THEN PRINT "screen image":RETURN 
 1310 PRINT "bytes"
 1320 PRINT "Start address: "; FN a(13)
 1330 PRINT "Length: "; FN a(11);" bytes"
 1340 RETURN 
 9998 SAVE "Tape EX" LINE 0

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