This package is a speech synthesizer system consisting of three components: a demonstration program, a word library compiler, and reusable speech and library-display subroutines. Words are stored as numeric indices in a machine code library residing in high memory around address 65022, and speech is triggered by writing a two-byte word number into fixed memory locations (65023–65024) and then calling the machine code routine at address 65077 via RANDOMIZE USR. The compiler program allows users to select words from one or more existing libraries, sorts selected word entries by their memory addresses, copies them using a machine code block-move routine at address 65195, and merges multiple sub-libraries into a single consolidated CODE file. The demo program shows the synthesizer counting from one to ten, speaking directional words while animating PLOT/DRAW graphics, and accepts free-text numeric sentences entered by the user. The SOUND keyword is used throughout for audio feedback chimes between operations.
Program Analysis
The listing contains three distinct programs that together form a speech synthesizer system. The first (lines 10–9010) is a demonstration program. The second (lines 1–9010, second occurrence) is a word library compiler. Shared subroutine blocks for speaking, library display, and the SOUND chime appear in both programs as well as in two additional standalone subroutine excerpts.
Program Structure
| Program | Key Line Range | Purpose |
|---|---|---|
| Demo | 10–110 | Animated speech demo: counting, directions, graphics |
| Demo (input mode) | 2000–2060 | User-entered numeric sentence playback |
| Demo (utilities) | 3000–9010 | Library display, speak subroutine, save/verify, SOUND chime |
| Compiler | 1–510 | Selects, sorts, copies, and merges word blocks from libraries |
| Compiler (utilities) | 3000–9010 | Same library display, speak, save/verify, chime subroutines |
| Standalone subroutines | 2000–4005 (×2) | Sentence parser and speak routine fragments |
Machine Code Interface
All speech output passes through a common BASIC subroutine at line 4002. It writes the word number NO as a big-endian 16-bit value into addresses 65023–65024, then writes the same value plus a two-byte header (length=1, data=0) into the low-memory pointer area starting at the address held in DW. The speech machine code is then invoked with RANDOMIZE USR 65077.
65077— main speech playback routine entry point65150— used in the compiler to retrieve the address of the most recently spoken word (USR 65150+4)65195— block-copy/move routine, called after POKEing source address, source length, and destination address into fixed parameter locations65207–65208— two-byte pointer toDW, the current low boundary of the word library in high memory65022— top of word library; the library is traversed downward from here
The word library itself is stored in high memory as a series of variable-length records. Each record consists of a two-byte word number, a two-byte length field, and the raw phoneme or audio data. The library display subroutine at line 3000 walks this structure downward: it reads NO from PEEK WA * 256 + PEEK (WA-1) and the block length NL from PEEK (WA-2) * 256 + PEEK (WA-3), then steps WA = WA - 4 - NL until WA <= DW.
Compiler Logic
The compiler (second program) lets the user select up to 50 words per library pass. After selection, the word entries are sorted in descending address order using a straightforward bubble sort (lines 200–230). Any word whose stored address falls at or below DW is flagged as not present and excluded (line 250). Selected words are then block-copied into a fresh library starting at address 65022 and growing downward using the machine code mover at 65195 (lines 300–350). The new bottom boundary is stored back into 65207–65208.
When multiple source libraries are specified (NL1 > 1), the compiler saves each sub-library as a CODE block, then reloads and merges them in order (lines 460–485). The merge step calculates offsets relative to 65022 and calls the block-move routine again to concatenate all selected words into a single contiguous library.
Sentence Parser
The sentence input routine at line 2000 appends a trailing space to the user’s input, then scans character by character for spaces. Each space-delimited token is converted to a number with VAL S$(B TO X-1) and spoken via GO SUB 4002. This means sentences are entered as space-separated word index numbers, not natural language text.
Demo Program Techniques
The demo at lines 10–110 uses a DATA statement at line 9010 containing the numeric word indices for all phrases spoken. RESTORE at line 12 resets the data pointer, and repeated READ NO: GO SUB 4002 loops consume words in sequence. Lines 37 prints each number on screen as it is spoken, providing a visual cue. Graphics animation (lines 50–90) uses PLOT and DRAW, then immediately redraws with INVERSE 1 to erase the line, creating a simple animation effect synchronized with spoken directional words.
SOUND Chime Subroutine
Line 8000 implements an audio feedback chime using two SOUND statements: SOUND 0,41;1,0;8,13;7,62 followed by a short PAUSE 5 and then SOUND 8,0;7,63. This subroutine is called between major operations (after saving, verifying, etc.) as a ready signal.
Notable Idioms and Anomalies
- The compiler uses a variable named
Bas both a loop scalar in the bubble sort (line 220) and as an arrayB()holding word numbers — these are distinct in BASIC but easy to confuse on reading. - Line 40 tests
A$="n" OR a$="N"— mixing case of the variable nameA$vsa$. In standard Spectrum BASIC, variable names are case-insensitive for numeric variables but string variable names are matched case-sensitively on some systems; both forms are used defensively here. - The variable
NLis used both as “number of libraries” in the compiler’s outer scope and as the per-record byte-length field in the library display subroutine (line 3020), which could cause a naming conflict if the display subroutine is called from within the compiler loop — this is a latent bug. - Line 9000 (
READ NO: GO SUB 4002: GO TO 9000) is an infinite loop for speaking all DATA items sequentially; it is never called from the main demo flow but serves as a utility entry point. - The variable
W1is computed at line 460 (LET W1=W+367) but never subsequently used — this appears to be dead code left over from development.
Content
Image Gallery
Source Code
10 REM *** DEMO PROGRAM *1984 SPEECH SYNTHESIZER
11 PRINT AT 10,10; FLASH 1;"ATTENTION"
12 RESTORE
15 GO SUB 8000: GO SUB 8000: PAUSE 20
20 GO SUB 8000: GO SUB 8000
30 FOR X=1 TO 2: READ NO: GO SUB 4002: NEXT X: PAUSE 45
31 CLS : PRINT "LISTEN AND WATCH THE DISPLAY AS I COUNT TO 10"
32 FOR X=1 TO 10: READ NO: GO SUB 4002: NEXT X: PAUSE 45
37 FOR X=1 TO 10: READ NO: PRINT NO;" ";: GO SUB 4002: NEXT X: PAUSE 45
38 PRINT '"I CAN COUNT TO A MILLION IF I WANT TO"
40 FOR X=1 TO 10: READ NO: GO SUB 4002: NEXT X: PAUSE 45
42 CLS : PRINT '"I AM THE COMPUTER, THIS HAPPENS TO BE MY NAME"
45 FOR X=1 TO 10: READ NO: GO SUB 4002: NEXT X: PAUSE 45
47 CLS : PRINT '"I CAN WATCH A PIXEL:"
50 FOR X=1 TO 5: READ NO: GO SUB 4002: NEXT X: FOR X=-10 TO 0: PLOT 127,40+X: DRAW -80,X: NEXT X
51 FOR X=-10 TO 0: PLOT INVERSE 1;127,40+X: DRAW INVERSE 1;-80,X: NEXT X
55 PRINT "LEFT"
60 READ NO: GO SUB 4002
70 READ NO: GO SUB 4002: FOR X=0 TO 10: PLOT 127,40+X: DRAW 80,X: NEXT X
71 FOR X=0 TO 10: PLOT INVERSE 1;127,40+X: DRAW INVERSE 1;80,X: NEXT X
75 PRINT "RIGHT"
80 READ NO: GO SUB 4002: PRINT "UP": FOR X=-10 TO 0: PLOT 127+X,77: DRAW X,70: NEXT X
81 FOR X=-10 TO 0: PLOT INVERSE 1;127+X,77: DRAW INVERSE 1;X,70: NEXT X
90 READ NO: PRINT "AND DOWN": GO SUB 4002: READ NO: GO SUB 4002: FOR X=0 TO 10: PLOT 150+X,77: DRAW X,-70: NEXT X
100 PAUSE 20: CLS : PRINT "BUT YOU DO NOT KNOW THIS,YOU ARE ___": FOR X=1 TO 10: READ NO: GO SUB 4002: NEXT X
110 STOP
2000 INPUT "ENTER SENTENCE TO BE SPOKEN ";S$: LET S$=S$+" "
2010 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 STOP
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 RETURN
4000 INPUT "enter # TO be spoken";no
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: RETURN
4010 POKE 65023,1: GO SUB 4005: POKE 65023,100: GO SUB 4005: STOP
5000 SAVE "DEMO"
5500 GO SUB 8000
6500 PRINT AT 11,8; FLASH 1;"READY TO VERIFY "
7000 VERIFY ""
7500 GO SUB 8000
7750 CLS : STOP
8000 SOUND 0,41;1,0;8,13;7,62: PAUSE 5: SOUND 8,0;7,63: RETURN
9000 READ NO: GO SUB 4002: GO TO 9000
9010 DATA 41,41,152,27,254,231,87,36,135,77,2,10,1,2,3,4,5,6,7,8,9,10,135,63,77,2,16,162,136,135,252,2,135,25,231,74,236,125,2,48,170,171,135,63,254,16,187,148,27,200,249,27,95,56,273,89,175,174,236,273,34,222
1 REM *** PROGRAM TO COMPILE LIBRARIES OF WORDS * 1984 ***
2 INPUT "ENTER THE NUMBER OF LIBRARIES YOU PLAN TO TAKE WORDS FROM ";NL1
3 DIM L(NL1)
5 FOR V=1 TO NL1
10 DIM A(50): DIM B(50)
15 REM ** ROUTINE TO EDIT LIBRARY **
20 INPUT "DO YOU WANT A LISTING OF THE LIBRARY THAT IS PRESENTLY IN MEMORY? ";S$: IF S$(1)="Y" OR S$(1)="y" THEN GO SUB 3000
22 LET T=0
25 LET dw=PEEK 65208*256+PEEK 65207
30 INPUT "ENTER # OF WORD YOU WISH TO USE (ENTER N AFTER LAST WORD) ";A$
40 IF A$="n" OR a$="N" THEN GO TO 200
50 LET T=T+1: LET NO=VAL A$
60 PRINT NO;" ";
70 GO SUB 4002
80 LET AD=USR 65150+4
90 LET B(T)=NO: LET A(T)=AD
100 GO TO 30
200 FOR X=1 TO T
210 FOR Y=X TO T
220 IF A(X)<A(Y) THEN LET B=A(Y): LET A(Y)=A(X): LET A(X)=B: LET B=B(X): LET B(X)=B(Y): LET B(Y)=B
230 NEXT Y: NEXT X
240 LET T1=T: FOR X=1 TO T1
250 IF A(X)<=DW THEN PRINT '"WORD ";B(X);" IS NOT IN LIBRARY": LET T=T-1
260 NEXT X
300 LET CW=65022: FOR X=1 TO T
310 LET C1=PEEK (A(X)-2): LET C2=PEEK (A(X)-3)
320 POKE 65197,C1: POKE 65196,C2: POKE 65200,INT (A(X)/256): POKE 65199,A(X)-(INT (A(X)/256)*256): POKE 65203,INT (CW/256): POKE 65202,CW-(INT (CW/256)*256)
335 RANDOMIZE USR 65195
340 LET CW=CW-4-(C1*256+C2)
350 NEXT X
360 LET DW=CW
370 POKE 65208,INT (CW/256): POKE 65207,CW-(INT (CW/256)*256)
375 IF NL1=1 THEN GO TO 450
380 PRINT FLASH 1;'"PREPARE TO SAVE": PRINT " CODE IS FROM ";CW;" TO 65389": PAUSE 60
390 SAVE "NEWLIB"CODE CW,(65389-CW)
400 GO SUB 8000
420 LET L(V)=DW: CLS
435 IF V=NL1 THEN GO TO 450
440 PRINT '"YOU MAY NOW LOAD ANOTHER LIBRARY": LOAD ""CODE : GO SUB 8000: NEXT V
450 IF NL1=1 THEN GO TO 490
455 PRINT '" READY TO COMBINE-LOAD PREVIOUS LIBRARIES IN ORDER"
460 FOR Z=1 TO NL1-1: LET W=DW-(65389-L(Z)): LET W1=W+367: LOAD ""CODE W,65389-L(Z)
465 LET DA=DW-367: LET Q1=65022-L(Z): LET C1=INT (Q1/256): LET C2=Q1-INT (Q1/256)*256
470 POKE 65197,C1: POKE 65196,C2: POKE 65200,INT (DA/256): POKE 65199,DA-(INT (DA/256)*256): POKE 65203,INT (DW/256): POKE 65202,DW-(INT (DW/256)*256): RANDOMIZE USR 65195
480 POKE 65208,INT ((DW-Q1)/256): POKE 65207,(DW-Q1)-(INT ((DW-Q1)/256)*256)
482 LET DW=PEEK 65208*256+PEEK 65207
485 NEXT Z
490 GO SUB 3000: PRINT '"READY TO SAVE"
500 SAVE "NEWLIB"CODE dw,65389-dw
510 STOP
3000 REM **ROUTINE TO DISPLAY LIBRARY**
3001 PRINT
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 RETURN
4000 INPUT "enter # TO be spoken";no
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: RETURN
4010 POKE 65023,1: GO SUB 4005: POKE 65023,100: GO SUB 4005: STOP
5000 SAVE "compile"
5500 GO SUB 8000
6500 PRINT AT 11,8; FLASH 1;"READY TO VERIFY "
7000 VERIFY ""
7500 GO SUB 8000
7750 CLS : STOP
8000 SOUND 0,41;1,0;8,13;7,62: PAUSE 5: SOUND 8,0;7,63: RETURN
9000 READ NO: GO SUB 4002: GO TO 9000
9010 DATA 1,134,70,9
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 RETURN
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: 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 RETURN
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: RETURN
1 REM *** PROGRAM TO COMPILE LIBRARIES OF WORDS * 1984 ***
2 INPUT "ENTER THE NUMBER OF LIBRARIES YOU PLAN TO TAKE WORDS FROM ";NL1
3 DIM L(NL1)
5 FOR V=1 TO NL1
10 DIM A(50): DIM B(50)
15 REM ** ROUTINE TO EDIT LIBRARY **
20 INPUT "DO YOU WANT A LISTING OF THE LIBRARY THAT IS PRESENTLY IN MEMORY? ";S$: IF S$(1)="Y" OR S$(1)="y" THEN GO SUB 3000
22 LET T=0
25 LET dw=PEEK 65208*256+PEEK 65207
30 INPUT "ENTER # OF WORD YOU WISH TO USE (ENTER N AFTER LAST WORD) ";A$
40 IF A$="n" OR a$="N" THEN GO TO 200
50 LET T=T+1: LET NO=VAL A$
60 PRINT NO;" ";
70 GO SUB 4002
80 LET AD=USR 65150+4
90 LET B(T)=NO: LET A(T)=AD
100 GO TO 30
200 FOR X=1 TO T
210 FOR Y=X TO T
220 IF A(X)<A(Y) THEN LET B=A(Y): LET A(Y)=A(X): LET A(X)=B: LET B=B(X): LET B(X)=B(Y): LET B(Y)=B
230 NEXT Y: NEXT X
240 LET T1=T: FOR X=1 TO T1
250 IF A(X)<=DW THEN PRINT '"WORD ";B(X);" IS NOT IN LIBRARY": LET T=T-1
260 NEXT X
300 LET CW=65022: FOR X=1 TO T
310 LET C1=PEEK (A(X)-2): LET C2=PEEK (A(X)-3)
320 POKE 65197,C1: POKE 65196,C2: POKE 65200,INT (A(X)/256): POKE 65199,A(X)-(INT (A(X)/256)*256): POKE 65203,INT (CW/256): POKE 65202,CW-(INT (CW/256)*256)
335 RANDOMIZE USR 65195
340 LET CW=CW-4-(C1*256+C2)
350 NEXT X
360 LET DW=CW
370 POKE 65208,INT (CW/256): POKE 65207,CW-(INT (CW/256)*256)
375 IF NL1=1 THEN GO TO 450
380 PRINT FLASH 1;'"PREPARE TO SAVE": PRINT " CODE IS FROM ";CW;" TO 65389": PAUSE 60
390 SAVE "NEWLIB"CODE CW,(65389-CW)
400 GO SUB 8000
420 LET L(V)=DW: CLS
435 IF V=NL1 THEN GO TO 450
440 PRINT '"YOU MAY NOW LOAD ANOTHER LIBRARY": LOAD ""CODE : GO SUB 8000: NEXT V
450 IF NL1=1 THEN GO TO 490
455 PRINT '" READY TO COMBINE-LOAD PREVIOUS LIBRARIES IN ORDER"
460 FOR Z=1 TO NL1-1: LET W=DW-(65389-L(Z)): LET W1=W+367: LOAD ""CODE W,65389-L(Z)
465 LET DA=DW-367: LET Q1=65022-L(Z): LET C1=INT (Q1/256): LET C2=Q1-INT (Q1/256)*256
470 POKE 65197,C1: POKE 65196,C2: POKE 65200,INT (DA/256): POKE 65199,DA-(INT (DA/256)*256): POKE 65203,INT (DW/256): POKE 65202,DW-(INT (DW/256)*256): RANDOMIZE USR 65195
480 POKE 65208,INT ((DW-Q1)/256): POKE 65207,(DW-Q1)-(INT ((DW-Q1)/256)*256)
482 LET DW=PEEK 65208*256+PEEK 65207
485 NEXT Z
490 GO SUB 3000: PRINT '"READY TO SAVE"
500 SAVE "NEWLIB"CODE dw,65389-dw
510 STOP
3000 REM **ROUTINE TO DISPLAY LIBRARY**
3001 PRINT
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 RETURN
4000 INPUT "enter # TO be spoken";no
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: RETURN
4010 POKE 65023,1: GO SUB 4005: POKE 65023,100: GO SUB 4005: STOP
5000 SAVE "compile"
5500 GO SUB 8000
6500 PRINT AT 11,8; FLASH 1;"READY TO VERIFY "
7000 VERIFY ""
7500 GO SUB 8000
7750 CLS : STOP
8000 SOUND 0,41;1,0;8,13;7,62: PAUSE 5: SOUND 8,0;7,63: RETURN
9000 READ NO: GO SUB 4002: GO TO 9000
9010 DATA 1,134,70,9
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.