--- title: "Tape Analyser" id: 53825 type: "computer_media" slug: "analyser" url: "http://localhost/computer_media/analyser/" markdown_url: "http://localhost/computer_media/analyser.md" published_at: "2024-05-13T19:02:00+00:00" modified_at: "2026-03-30T21:45:53+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/05/tape-analyzer.png" excerpt: "A tape analyzer utility reads cassette headers one by one and displays each file's name, type, length, and load address using a tiny embedded machine code loader." 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: "William Pierson" slug: "william-pierson" taxonomy: "indiv" url: "http://localhost/indiv/william-pierson/" genre: - name: "Tape" slug: "tape" taxonomy: "genre" url: "http://localhost/type/tape/" - name: "Utility" slug: "utility" taxonomy: "genre" url: "http://localhost/type/utility/" 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/" media_type: "Program" programmers: - name: "William Pierson" slug: "william-pierson" taxonomy: "indiv" url: "http://localhost/indiv/william-pierson/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/05/tape-analyzer.png" media_type_tags: "Tape, Utility" --- # Tape Analyser This program is a tape analyzer utility that reads cassette tape headers and displays information about each file stored on the tape, including file name, length, type, and relevant metadata. It uses a 16-byte machine code routine POKEd into memory at address 30000, which calls the ROM’s tape-loading header routine at address 1366 (05 56 hex) and jumps to address 30000 on success. The BASIC then interprets the header block stored at address 30016 (held in variable `p`) to identify whether each file is a BASIC program, a numeric or string array, a CODE block, or a read error, and formats the output accordingly. It detects SCREEN$ blocks by checking for a CODE file starting at 16384 with length 6912. The program works across both ZX Spectrum and TS2068 hardware when in Spectrum mode. *** ## Program Analysis ### Program Structure The program is organized into a clear sequence of phases: initialization, machine code installation, user interface setup, and a main loop that repeatedly reads tape headers and classifies them. 1. **Lines 10–11:** Set display attributes and clear memory up to 29999. 2. **Lines 15–20:** Define a helper function `FN p(x)` for reading 16-bit little-endian values, set pointer variable `p` to 30016, and POKE 16 machine code bytes starting at address 30000 (byte offset +1, since index starts at 1). 3. **Line 30:** The DATA bytes for the machine code routine. 4. **Lines 40–65:** Draw the UI header, title bar, column headings, and prompt the user to start the tape. 5. **Lines 70–230:** Main loop — call machine code, parse the header buffer, classify and print each file’s details, then loop back to line 70. ### Machine Code Routine The 16 DATA bytes at line 30 are POKEd to addresses 30000–30015 (the loop runs `i=1 TO 16`, POKEing to `29999+i`). Disassembly of the routine: | Address | Bytes (hex) | Instruction | Notes | | --- | --- | --- | --- | | 30000 | DD 21 40 75 | LD IX, 30016 | Point IX at header buffer (30016 = 0x7540) | | 30004 | 11 11 00 | LD DE, 17 | Header block is 17 bytes | | 30007 | AF | XOR A | A=0 signals “load header” | | 30008 | 37 | SCF | Carry set = LOAD (not verify) | | 30009 | CD 56 05 | CALL 1366 | ROM LD-BYTES tape routine | | 30012 | D8 | RET C | Return if successful (carry set) | | 30013 | C3 30 75 | JP 30000 | Loop back on failure/timeout | On successful return, the 17-byte tape header is stored at address 30016 in the standard Spectrum format: byte 0 is the type, bytes 1–10 are the filename, bytes 11–12 are data length, and bytes 13–16 hold type-specific parameters. ### Header Parsing and File Classification After the machine code returns, BASIC at line 80 reads the type byte and filename from the buffer at `p` (30016). The `FN p(x)` function reads a 16-bit word from address `x` using `PEEK x + 256*PEEK(x+1)`. The four standard Spectrum tape block types are handled: - **Type 0 (BASIC):** Lines 200–230 extract the autostart LINE and program length and print them. - **Type 1 or 2 (Array):** Lines 180–190 decode the variable name letter from the descriptor byte at `p+14`, constructing an array name that includes the program name for string/number arrays (types 1 and 2 both add the filename). - **Type 3 (CODE):** Lines 140–160 check for a SCREEN$ (load address 16384, length 6912) and print the load address for other CODE blocks. - **Type >3 (Error):** Line 110 prints “Read Error” for any unrecognized type byte. ### Key BASIC Idioms and Techniques - **Two-byte PEEK function:**`DEF FN p(x)=PEEK x+256*PEEK(x+1)` at line 15 is a standard idiom for reading 16-bit little-endian integers from memory. - **Right-justified length field:** Line 90 constructs a right-justified 6-character length string using `" "(1 TO 6-LEN l$)+l$`, a common Spectrum BASIC string-padding trick. - **Array name decoding:** At line 180, the array descriptor byte at `p+14` encodes the variable type in the high bits and the letter in the low 5 bits. `INT(s/32)` extracts the type and `s-32*t+96` recovers the lowercase letter (ASCII offset from 96 to get ‘a’–’z’). - **Loop re-entry via GO TO 70:** Rather than using a BASIC `FOR` loop, the program uses explicit `GO TO 70` at the end of each branch to restart the machine code call, allowing the loop to continue indefinitely without stack growth. - **Double apostrophe in PRINT:** Line 50 uses `''` after the column headers to produce two newlines, separating the header from the file list. ### Notable Observations and Anomalies - The `RANDOMIZE USR 30000` at line 70 is a standard idiom for calling machine code that is expected to return normally; `RANDOMIZE` suppresses any error if USR returns a non-zero value. - Variable `p` is set to 30016 at line 15 inside the `DEF FN` statement using a comma-separated `LET`, which is a compact way to initialize a variable on the same line as a function definition. - The machine code loop at address 30013 (`JP 30000`) means that if the tape load fails or times out, the routine retries internally without returning to BASIC — the BASIC loop only sees successful header reads. - Line 65 uses `PRINT #1;AT 1,0,,` which clears the lower screen prompt after the keypress, though the trailing commas may produce unexpected formatting on some firmware versions. - Line 180 checks `t=2 OR t=6` to decide whether to append the program name to the array variable letter. The Spectrum variable descriptor uses bits 5–7 for type: value 2 corresponds to string arrays and value 6 may be an extended or TS2068-specific array type. ## Source Code ``` 1 REM 'tape analyser' for Spectrum mode but works for 2068 or Spectrum tapes 2 REM Bill Pierson 10 BORDER 0: PAPER 0: INK 7 11 CLEAR 29999 15 DEF FN p(x)=PEEK x+256*PEEK (x+1): LET p=30016 20 FOR i=1 TO 16: READ a: POKE 29999+i,a: NEXT i 30 DATA 221,33,64,117,17,17,0,175,55,205,86,5,216,195,48,117 40 PRINT INVERSE 1;" *Tape Analyser* "; INVERSE 0 50 PRINT " NAME";TAB 10;"Length Remarks"'': PLOT 0,156: DRAW 255,0 60 PRINT #1;" Start tape, then press any key": PAUSE 0 65 PRINT #1;AT 1,0,, 70 RANDOMIZE USR 30000 75 LET n$="" 80 LET type=PEEK p: LET l=FN p(p+11): FOR i=p+1 TO p+10: LET n$=n$+CHR$ PEEK i: NEXT i 90 LET l$=STR$ l: LET l$=" "(1 TO 6-LEN l$)+l$ 100 PRINT n$;l$;" "; 110 IF type>3 THEN PRINT "Read Error": GO TO 70 130 IF type<3 THEN GO TO 170 140 LET s=FN p(p+13) 150 IF s=16384 AND l=6912 THEN PRINT "SCREEN$ ": GO TO 170 160 PRINT "CODE at: ";s: GO TO 70 170 IF type=0 THEN GO TO 200 180 LET s=PEEK (p+14): LET t=INT (s/32): LET l$=CHR$ (s-32*t+96): IF t=2 OR t=6 THEN LET l$=l$+n$ 190 PRINT "Array: ";l$: GO TO 70 200 LET l=FN p(p+13): LET s=FN p(p+15) 210 PRINT "Basic"; 220 IF l<10000 THEN PRINT " LINE ";l; 230 PRINT : GO TO 70 ```