This program is a speech synthesis editor that manages a digitized word library stored in high memory, for use with Tad Painter’s Speech Synthesizer program, roughly from address 55000 to 65535.
It provides routines to record words (via machine code at addresses such as 65025, 65042, 65063, 65077, and 65080), play them back, adjust acquisition speed, and save or load the library to and from tape using SAVE/LOAD CODE.
A graphical memory browser (lines 2–65) uses PLOT and DRAW with PEEK to render a waveform of raw byte values at any 256-byte page of memory, with a cursor controlled by the “8” and “5” keys.
The program encodes word entries as 4-byte records (start address, length) in a descending linked list in high memory, with a block-copy routine at USR 65195 used to pack data toward a moving destination pointer tracked in locations 65207–65208.
Included with the HS68 digitizing hardware.
Program Analysis
Program Structure
The program is organized into loosely coupled functional blocks rather than a single linear flow. Execution begins with GO SUB 9000 (initialization) then falls through to GO TO 9500 (the main menu). From the menu the user types a numeric address to jump to any module. The blocks are:
- Lines 2–65: Interactive memory waveform browser
- Lines 14–20: Low-level utility entry points (machine code launchers, POKE configuration, manual byte entry)
- Lines 50–75: Waveform browser keypress handler and word-marking logic; also the block-copy subroutine (line 70/71–75)
- Lines 2000–2060: Sentence-to-speech routine — tokenizes a string on spaces and speaks each word number
- Lines 3000–3070: Library display routine — walks the linked list of word records
- Lines 3999–4010: Core speak routine — POKEs the word number and calls USR 65077
- Lines 6000–7800: Save, verify, and load routines for the word library CODE block
- Line 8000: SOUND fanfare subroutine
- Lines 8500, 9000–9003: Destination-pointer reset and initialization
- Lines 9500–9540: Main menu display
- Lines 9998–9999: Data erase and program save utilities
Machine Code Interface
All speech and data operations are delegated to machine code routines residing in high RAM. The BASIC communicates with them exclusively through POKEs into fixed parameter addresses before calling RANDOMIZE USR. The known entry points and their apparent roles are:
| Address | Role | Parameters (POKEd addresses) |
|---|---|---|
| 65025 | Playback | 65096, 65117 (play speed) |
| 65042 | Record acquisition speed POKE target | set via line 18 |
| 65077 | Speak word | 65023–65024 (word number), 65207–65208 (dest ptr) |
| 65080 | Unknown utility (line 15) | — |
| 65195 | Block memory copy (descending) | 65196–65197 (count), 65199–65200 (source), 65202–65203 (dest) |
All 16-bit values are split into high and low bytes with the standard BASIC idiom: INT(n/256) for the high byte and n - 256*INT(n/256) for the low byte, then POKEd to consecutive addresses in little-endian order.
Waveform Memory Browser
Lines 2–65 implement a rudimentary oscilloscope-style display. For a given 256-byte memory page starting at address q, the loop at line 3 steps through bytes three at a time (STEP 3). Each byte is read with PEEK and its value, divided by 1.5 to fit the screen height, is used as the length of a vertical DRAW 0, ... line rooted at the corresponding x position with PLOT INVERSE 1. This gives a rough amplitude graph of raw byte data — useful for locating speech waveform boundaries. The variable over tracks a cursor position (initialized to 127, the center of the 256-byte window), and pressing “8” or implicitly “5” (line 60, the fall-through case) moves it right or left. The current address and its PEEK value are shown at the top of the screen (lines 65).
Word Library Data Structure
The word library is stored as a descending linked list of 4-byte records in high memory, with the list head near address 65022 and growing downward toward 55000 (the workspace boundary noted in line 14). Each record contains:
- 2 bytes: word number (big-endian at
WA,WA-1) - 2 bytes: payload length
NL(big-endian atWA-2,WA-3)
The library walker at line 3050 advances with LET WA = WA - 4 - NL, skipping over the word data to reach the next record header. The word-marking logic in lines 57–58 writes a new record at stw (start-of-word address) during interactive browsing of the waveform.
Sentence-to-Speech Routine
Lines 2000–2060 implement a simple tokenizer. A trailing space is appended to S$, and the loop scans for space characters. When one is found, VAL S$(B TO X-1) extracts the numeric token and passes it as NO to the speak subroutine at line 4002. This allows a user to enter a sequence of word-index numbers separated by spaces and have them spoken in order — a sentence-by-number approach rather than text-to-speech.
SOUND Fanfare
Line 8000 uses SOUND with multiple channel/register arguments to produce a short musical cue, followed by PAUSE 60 and a second SOUND call to stop the tone. This subroutine is called after save and verify operations as an audible completion signal.
Notable Idioms and Techniques
GO TO v/GO TO gafter anINPUTprompt is used throughout as a manual dispatcher — the user types a line number to navigate between modules, replacing a conventional menu loop.POKE 23609,15in line 9003 sets the ZX Spectrum system variable REPDEL to slow down key repeat, relevant for the cursor-movement loop in the waveform browser.- Line 58 contains dead code: after
GO TO 3, the fragmentINPUT "go where? ";g: GO TO gis unreachable but was presumably left from an earlier version. - The variable
CK(initialized in line 9003 and set in lines 57–58) acts as a one-bit state flag to ensure that a word-end marker (“e”) is only accepted after a word-start marker (“s”) has been placed. - Line 9998 erases the data file by POKEing zeros from 65022 down to 55000 in a
FOR/NEXTloop — a slow but straightforward wipe. - The
SAVE "BLANKLIB" CODE 65022,513in line 9999 saves 513 bytes of the (presumably zeroed) library area as a blank template file.
Bugs and Anomalies
- Line 6 is
GO TO 50, but line 50 checksINKEY$=""in a tight loop. AfterPAUSE 160, any key still held from the previous action could immediately exit the wait — potentially skipping a screen. - Line 4 computes
DRAW 0, PEEK(q-j)/1.5with no bounds check. For large PEEK values (e.g., 255), the draw length of 170 pixels could run off the top of the display. - The variable
dvis computed in line 4006 but never used anywhere visible in the listing — it may feed into machine code through a POKE elsewhere that was omitted, or it may be vestigial. - Line 57 marks the start of a word with
LET stw = over + q. Sinceoverranges 0–255 andqis a page-aligned address, this correctly computes the absolute address, but the variable namestwis also used in the block-copy subroutine (line 70) as the source start address — sharing the name across two different semantic roles is a potential source of confusion.
Content
Source Code
1 GO SUB 9000: GO TO 9500
2 INPUT "enter addrss to be displayed";n
3 LET over=127: FOR q=n TO 0 STEP -256: CLS : PRINT AT 0,0;q: FOR j=0 TO 255 STEP 3
4 PLOT INVERSE 1;255-j,4: DRAW 0,PEEK (q-j)/1.5
5 NEXT j: PAUSE 160
6 GO TO 50
14 REM **** work space is from 31488 to 36000 ****
15 RANDOMIZE USR 65080: INPUT "go where? ";v: GO TO v
16 RANDOMIZE USR 65025: BEEP .1,50
17 POKE 65087,140: POKE 65086,160: POKE 65126,122: POKE 65125,255: INPUT "go where? ";g: GO TO g
18 INPUT "speed of aquisition for record? ";sq: POKE 65042,sq: POKE 65063,sq: INPUT "speed of aquisition for play? ";sq: POKE 65096,sq: POKE 65117,sq: INPUT "go where? ";v: GO TO v
20 INPUT "enter start addrss for code ";sta: FOR x=sta TO 65535: INPUT "byte? ";byte: PRINT x,byte: POKE x,byte: NEXT x: STOP
50 IF INKEY$="" THEN GO TO 50
52 PLOT INVERSE 1;over,3
53 LET i$=INKEY$
55 IF i$="8" THEN LET over=over+1: GO TO 65
56 IF i$="m" THEN GO TO 9500
57 IF i$="s" THEN LET CK=1: LET stw=over+q: INPUT "enter # of this word ";now: POKE stw,INT (now/256): POKE stw-1,now-256*INT (now/256)
58 IF i$="e" AND CK=1 THEN LET CK=0: LET enw=over+q: LET nob=stw-enw: POKE stw-2,INT (nob/256): POKE stw-3,nob-4-256*INT (nob/256): GO SUB 70: LET no=now: GO SUB 4002: LET n=enw-256: GO TO 3: INPUT "go where? ";g: GO TO g
59 IF i$="n" THEN LET over=127: LET n=n-256: GO TO 3
60 LET over=over-1
65 PLOT over,3: PRINT AT 0,0;q-255+over: PRINT AT 1,0;PEEK (q-255+over);" ": GO TO 50
69 INPUT "enter strt addrs for data TO be trnsfrd (decsending)";stw: INPUT "enter # of bytes";nob: INPUT "destination adrss?";dw: GO TO 71
70 REM nob is # of bytes,stw is start addrs,dw is destin addrs
71 LET dq=dw: POKE 65197,INT (nob/256): POKE 65196,nob-256*INT (nob/256)
72 POKE 65200,INT (stw/256): POKE 65199,stw-256*INT (stw/256)
73 POKE 65203,INT (dw/256): POKE 65202,dw-256*INT (dw/256)
74 LET dw=dw-nob: RANDOMIZE USR 65195
75 POKE 65208,INT (DW/256): POKE 65207,DW-INT (DW/256)*256: PRINT "dw= ";dw: RETURN
2000 INPUT "ENTER SENTENCE TO BE SPOKEN ";S$
2010 LET S$=S$+" ": LET B=1: FOR X=1 TO LEN S$
2020 IF S$(X TO X)=" " THEN LET NO=VAL S$(B TO X-1): GO SUB 4002: LET B=X
2050 NEXT X
2060 RETURN
3000 REM **ROUTINE TO DISPLAY LIBRARY**
3005 LET WA=65022
3010 LET NO=PEEK WA*256+PEEK (WA-1)
3020 LET NL=PEEK (WA-2)*256+PEEK (WA-3)
3030 PRINT NO;" ";
3040 GO SUB 4002
3050 LET WA=WA-4-NL
3060 IF WA>DW THEN GO TO 3010
3065 PRINT
3070 INPUT "go where? ";g: GO TO g
3999 INPUT "enter # TO be spoken";no
4000 REM ** ROUTINE TO SPEAK-LET NO=DESIRED WORD **
4002 POKE 65024,INT (no/256): POKE 65023,no-(INT (no/256)*256)
4003 LET dw=PEEK 65208*256+PEEK 65207: POKE dw,INT (no/256): POKE dw-1,no-(INT (no/256)*256): POKE dw-2,0: POKE dw-3,1
4005 RANDOMIZE USR 65077
4006 LET dv=PEEK 65125+256*PEEK 65126
4010 RETURN
6000 INPUT "ENTER NAME OF LIB TO BE SAVED";N$: SAVE N$CODE dw,65535-dw
6010 GO SUB 8000
6500 INPUT "DO YOU WANT TO VERIFY? ";V$: IF V$(1,1)="Y" THEN PRINT AT 11,8; FLASH 1;"READY TO VERIFY ": GO TO 7000
6750 INPUT "GO WHERE? ";G: GO TO G
7000 VERIFY "": VERIFY ""CODE
7500 GO SUB 8000
7750 CLS : INPUT "GO WHERE? ";G: GO TO G
7800 INPUT "ENTER NAME OF LIB TO BE LOADED OR ENTER";N$: LOAD N$CODE
8000 SOUND 0,41;1,0;8,13;7,62: PAUSE 60: SOUND 8,0;7,63: RETURN
8500 LET dw=dq: POKE 65208,INT (dw/256): POKE 65207,dw-INT (dw/256)*256: INPUT "go where? ";g: GO TO g
9000 LET dw=PEEK 65208*256+PEEK 65207
9001 LET r=16: LET p=15: LET a=18: LET s=6000: LET M=9500: LET D=2: LET WS=36100: LET C=8500
9002 LET H=3000: LET L=7800
9003 LET ck=0: POKE 23609,15
9010 RETURN
9500 CLS : PRINT ''TAB 7;"M-MENU:"''
9502 PRINT TAB 8;"R-RECORD"
9504 PRINT TAB 8;"P-PLAYBACK"
9506 PRINT TAB 8;"A-ADJUST RATE"
9508 PRINT TAB 8;"S-SAVE LIBRARY"
9509 PRINT TAB 8;"L-LOAD LIBRARY"
9510 PRINT TAB 8;"H-HEAR LIBRARY"
9511 PRINT TAB 8;"C-CORRECT LAST MISTAKE"
9513 PRINT TAB 8;"______________"''
9514 PRINT TAB 8;"D-DISPLAY WORD:"''
9515 PRINT TAB 9;"M-CALL MENU"
9516 PRINT TAB 9;"N-NEXT SCREEN"
9518 PRINT TAB 9;"S-MARK START OF WORD"
9520 PRINT TAB 9;"E-MARK END OF WORD"
9521 PRINT TAB 9;"8-SCAN RIGHT/5-LEFT"
9522 PRINT TAB 9;"wS=START OF WORK SPACE"
9540 INPUT "GO WHERE? ";G: GO TO G
9600 STOP
9998 INPUT "DO YOU WANT TO ERASE DATA FILE?";Y$: IF Y$="Y" THEN FOR x=65022 TO 55000 STEP -1: POKE x,0: NEXT x: STOP
9999 SAVE "SPEAK EDIT": GO SUB 8000: SAVE "BLANKLIB"CODE 65022,513: GO SUB 8000: STOP
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.



