--- title: "Speech Synthesizer" id: 51775 type: "computer_media" slug: "speech-synthesizer" url: "http://localhost/computer_media/speech-synthesizer/" markdown_url: "http://localhost/computer_media/speech-synthesizer.md" published_at: "2023-08-13T23:14:31+00:00" modified_at: "2026-04-03T07:59:16+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A complete speech synthesizer toolkit featuring a dramatic counting demo, free-text sentence input, and a multi-library word compiler that merges speech data blocks in high memory." 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: "Tad Painter" slug: "tad-painter" taxonomy: "post_tag" url: "http://localhost/tag/tad-painter/" - 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/" genre: - name: "Speech Synthesis" slug: "speech-synthesis" taxonomy: "genre" url: "http://localhost/type/speech-synthesis/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Speech Synthesizer (1984)(Tad Painter)(US)(TS2068)(Cassette).zip" mediadate: "1984" producer_company: - id: 11380 title: "Tad Painter" type: "company" url: "http://localhost/company/tad-painter/" related_products: - id: 14012 title: "Speech Synthesizer (2068)" type: "product" url: "http://localhost/product/speech-synthesizer/" media_type_tags: "Speech Synthesis" --- 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 point - `65150` — 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 locations - `65207–65208` — two-byte pointer to `DW`, the current low boundary of the word library in high memory - `65022` — 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 `B` as both a loop scalar in the bubble sort (line 220) and as an array `B()` 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 name `A$` vs `a$`. 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 `NL` is 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 `W1` is computed at line 460 (`LET W1=W+367`) but never subsequently used — this appears to be dead code left over from development. ## 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)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)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 ```