Sitenotice: 11/29/2018: The wiki is back. It turns out, some anti-virus product on my web server had an issue with the latest version of PHP. My server techs have resolved this issue, and things should be working again. During the investigation, I did restore to a backup from September. There is a chance that any changes done since then were lost, but I do not recall any edits. --OS-9 Al

8/30/2016: Massive re-work is being done on the InfoBox Templates. Read that page to keep up with the plan for that, and adding better keyword tags (categories) to all the pages. --OS-9 Al (talk) 15:28, 31 August 2016 (CDT)

Undercolor/840102/A RealTime Talking Clock

From CoCopedia - The Tandy/Radio Shack Color Computer Wiki
Jump to: navigation, search

Home Articles Companies Publications Hardware People Software Timeline ... Emulators Internet Resources
(Don't see something listed? Click "edit" and add it! Together we can build this database. When making a new info page, refer to this InfoBox Template for guidelines.)



UnderColor, Volume 1, Number 2, December 25, 1984

  • Title: A RealTime Talking Clock
  • Author: Dennis Kitsz
  • Synopsis: Part II of an ongoing series.
  • Page Scans: Link

Article

The idea of computer-generated speech always reminds my of my elementary school days, when Bell Laboratories amazed the world with its first talking computer. It sang Daisy in a hollow, inflectionless voice. l heard that same voice about ten years later in 2001: A Space Odyssey. Director Stanley Kubrick paid homage to that first digital talker as the errant supercomputer HAL is dismantled, board by electronic board. It first begins to lose its intelligence and other human traits; its speech becomes simpler, then mindless. Finally HAL begins singing Daisy—in that hollow, inflectionless voice. Bell Labs’ Daisy is nearly a quarter-century past. Last month I presented the concepts of digitally recording your own speech, and this month I’ve got a simple circuit that will provide you with high-quality speech with a built-in clock vocabulary. With just a little more work, a speech device with full, software-controlled inflection can be built.

At the heart of this speechmaking is the General Instrument

SPO256 vocal tract synthesizer circuit. Vocal tract synthesizers work by emulating the vocal tract— lungs, vocal cords, throat, nose, mouth, tongue and lips. Every word makes use of a significant portion of the vocal tract, which can be thought of as a tone generator, a noise generator, and a filter.

Your Personal Synthesizer

To see how your built-in generators and filters work, try this. Hum a note in the middle of your voice range—the pitch generator at work. Now sing "eeee" on that note. Change that sung "eeee" slowly to an "oooo" sound on the same note. Now stop the note and produce the unvoiced "th" sound, like you find at the end of "myth."

Hook them together slowly: "eeee" changing the "oooo" ending with "th." That’s the first step. You've sounded a note, and filtered it with your mouth as "eeee" changed to "oooo." The "th" sound is your noise generator. The next step is to change the pitch. Again, sing "eeee" changing to "oooo," but make the pitch drop as you speak, that is, slide from a higher to a lower singing note while singing "eeee- oooo." End it in "th" again; EEEE·OOooo-th.

The final step is to shorten the "eeee" sound, and put the whole thing together quickly-pitch, filtering and noise. EE·OOoooo·th. Eooth. Youth. There's the word: youth. It consists of a sliding tone, a changing filtered sound, and a bit of noise.

Even the most complex sounds can be broken down into basic chunks of sound. There are the filtered pitches such as ee, oo, oh, ah, ih, eh; noises like th, f, s, sh; and filtered voice-pitch combinations that produce b, p, m, n, l, r, ch and so forth. Pitch changes provide inflection to the words. All these pieces are called allophones, and can be emulated with electronic pitch generators, noise generators, and filters. Such electronic allophones are the basis of many speech synthesis circuits, such as the General Instrument SP0256 device that interfaces so easily with the Color Computer. lnside the SP0256 are the sound generators and filters, plus a small microprocessor which, given sets of instructions, can produce complex, intelligible sound.

lnterfaclng The Synthesizer

The SPO256-017 and its companion SPR016-117 serial speech ROM form a talking clock set; these are the most accessible speech synthesis parts, available for $10 from Radio Shack (catalog number 276-1783). The interface schematic is shown in Figure 1. It uses the CoCoPort

input/output board (Custom Color, April-May, 1983), making it compatible with disk drives and cartridges. The entire Color Computer talking clock can be constructed for under $25.

Construction is simple, using wire-wrapping or point-to-point soldering. Use sockets for all integrated circuits, and be cautious when handling the synthesizer chip and speech ROM. These are static-sensitive parts, and should be left in their packages until you are ready to put them in their sockets. Sufficient power can be

obtained from the computer itself (5 volts is found on pin 9 of the Color Computer's edge connector).

The final step is getting the clock talking. Listing 1 contains the complete interrupt—driven clock display software

presented last time, but integrated into this listing is a driver for the speech board. Have a look at the listing, beginning at the speech driver (Line 940), and the pin diagram (Figure 2).

The overall point of this series is timing, so consider that there's one important requirement for speaking, since in computer terms speaking is a very slow process: your program has to know when the last spoken word is complete. The SP0256 accepts a signal on ALD (Address Load, pin 20) to tell it to start talking, and provides the LRQ (Load Request, pin 9) to inform the host computer when its in-

put buffer is full and cannot accept any

more information.

The program in Listing 1 accommodates these special signals, and thus addresses the demands of two real-time problems: maintaining the correct real time, and keeping track of when the speech synthesizer is ready to speak. As

you can imagine, the interrupt process — with its regular, predictable, short bursts of programming — is the ideal way of making both timekeeping and speechmaking invisible to any other programs which may be running.

Turn first to the flowchart in Figure 3, which outlines the sequence needed to announce the time. The actual interrupt-driven timekeeping was described in the

first part of this project.

Talking Is Tricky

Speaking the time is second nature to

us, but it turns out to be a tricky programming task. Consider that from the number 0-20 there are unique written names for each number (zero, one, two... eighteen, nineteen, twenty), but after that only every tenth digit (thirty, forty, fifty, etc.) has a unique name. Hours only need the numbers through twelve, but minutes need the numbers through 59.

Furthermore, leading zeroes (such as 01:15) have to be quashed (you'd say one-fifteen, not oh-one-fifteen), although internal zeroes (11:03) have to be said aloud (eleven-oh-three, not eleven-three). For a 12-hour clock a.m. and p.m. need to be indicated, and exact hours (04:00, for example) have to spoken correctly (four a.m. rather than four-oh-oh a.m.). Counting anomalies have to be handled correctly; for example, the hour after 11:15 p.m., though a larger numerical value, is actually 12:15 a.m.

The original time display from last month was military time; I’ve kept that display. But I’ve decided that the spoken time is to be in ordinary a.m.-p.m. format, so 12 hours must be subtracted from any displayed hours over 12 to obtain the spoken hours: 13:17 becomes 1:17 p.m.

In other words, all options must be checked and any of several different paths might be followed for the correct expression of different times through the day. It's not difficult to organize — children do it easily — but it is tedious, boring programming.

Program Description

For a run-through, look back at Listing 1. The talking clock subroutine is dormant until triggered, which happens when a full minute rolls over during the software clock interrupt service routine; this roll-over trigger is found at Line 580. (If you like, you change which time roll-over triggers the talking clock. Move the position of LBSR CLOXER from Line 580 to Line 535 for 10 second announcements; to Line 605 for 10 minute speaking; or to Line 635 for one hour timing.) At the roll-over point, the subroutine Cloxer is entered (Line 1080), which saves all registers, sets the clock-talking flag CLKON, and checks to be sure the synthesizer is ready to speak. At this time, the phrase "It is..." is triggered and the announcement begins.

The Cloxer subroutine serves a double purpose, so after the "It is..." announcement, verbal descriptions get a little hazy. If the clock is talking (that is, sound is being output and the synthesizer’s buffer is full), this subroutine returns to the main interrupt service subroutine (the time keeper), finishes its work, and resumes

normal computing in Basic. If the clock is not talking or there's still room in the buffer, the program loads the value stored at WHICH and makes an indirect jump to the program steps that properly complete the subroutine. The first time through, at the one minute roll-over, the value at Which is zero, and the phrase "It is..." is spoken.

I talked about indirect jumps in my article on the Game of Life (April, 1984), so if you're not familiar with them, review that discussion. In general, the Which value provides an offset to any one of four program segments: routine It is, which speaks the words "It is..."; routine Hours, which announces the current hour; routine Minutes, which announces the current minute; and routine Ampm, which speaks the phrase "a.m." or "p.m." Separate routines are needed because the speech synthesizer’s buffer isn't large enough to handle an entire time phrase such as "It is eleven forty-five p.m." at

once. Naturally, the program can't wait for the speech to be completed, or one of the values of a fast, interrupt-driven subroutine would be sacrificed. A companion need for the individual subroutine segments is that, after having triggered the speech synthesizer to speak each phrase and return to the main program, the routine has to be able to continue

where it left off. Otherwise, it might find itself saying "It is... it is... it is... it is... it is..."

Once the indirect jump JMP <A,Y> is made to one of the four possible program branches from Line 1210, the program is off and running according to the flowchart in Figure 3.

Recall that I mentioned the main subroutine is first triggered by the one-minute roll-over when it enters the service routine Cloxer, and begins the phrase "It is...." Back at Line 950 are three indicators: the Ampm flag for a.m. and p.m., the value Which to identify that the speaking routine is in progress, and the flag CLKON to define whether or not the clock is presently announcing.

Once the announcing starts, however, the interrupt service routine is subsequently handles by Line 990 — before

Cloxer — where the routine CLKTST is found. CLKTST evaluates announcement-in-progress flag CLKON every tenth of a second (branching from Line 470). If the synthesizer is not in the midst of announcing, CLKTST merely returns to the main interrupt service routine. If the clock is supposed to be talking — which could only have been triggered by the execution of Cloxer at the one-minute rollover — the CLKTST routine executes Cloxer.

But this time the circumstances are different. Cloxer would already have done some work (the Itis routine) and would be

ready to go on to the other speaking roles (hours, minutes, am/pm). By the way, the complete set of words and phrases

available to the SP0256-017/SPR16-117 speech synthesis pair is shown in Table 1. The multiple use of Cloxer is tricky and doesn't lent itself to quick descriptive summaries. Fortunately, it’s not necessary to understand my logic in retail unless you want to modify or expand my program. In summary, the interrupt service routine executes CLKTST to check (every 1/10 second) for clock synthesizer announcements in progress. Usually nothing happens, but at the roll-over of one minute, the interrupt service routine executes Cloxer to say "It is..." and to spark the clock announcement process into action. The CLKON flag is set. After the announcement is begun and when the synthesizer buffer becomes available, CLKTST continues to execute Cloxer and its four indirectly accessed subroutines until all the synthetic speaking is complete. Finally, the CLKON flag is turned off until the next one minute roll-over triggers the process anew.

Using The Program

Enter the source code in Listing 1 using an editor/assembler. Save the source to tape (W TALKCLOK), assemble the program to tape (A CLOXER), and turn the computer off. Remove the Edtasm cartridge, insert the talking clock board, turn the machine on, protect memory (CLEAR 200,&H3E00), load the assembled program from tape (CLOADM "CLOXER), and execute it (EXEC&H3E00). The program will patch into the interrupt routine, the clock will be displayed on the screen, and the clock will begin announcing each minute.

You'll want to set the clock, which will initialize to 00:00:0.0. Load and execute the short Basic time—setting program in

Listing 2. You can now use the talking clock with your Basic programs and it will provide a display and audible output for

the correct time of day until you turn the machine off, or use program cartridges or assembly language programs which

occupy the same memory area as the clock software. 32K and 64K computer users can relocate the clock in other areas of memory as needed.

Note that if you press the Reset button the peripheral interface adaptor on the clock board will be reset as well. Use EXEC&H3E00 to restore the operation of the

speech synthesizer.

The SND Input

As shown in the schematic, the output of the speech synthesizer feeds the input of an external amplifier and speaker. It can, however, by made to sound through

your television speaker. Connect the output of the speech synthesizer to pin 35 of the Color Computer's edge connector. Then enter the following line:

X = &HFF00:POKEX+1,PEEK(X+1)AND247:POKE X+3,PEEK(X+3)OR 8:POKE X+35) OR 8

The sound will now feed through to the television speaker. This sound input is useful whenever you want to mix sound from an external device with the sound from the computer. For example, other kinds of speech and sound boards, analog input devices, and so forth, can be fed through the SND input. The Basic Sound and Input/Output commands and the Reset button reset the original sound conditions, though, so you'll have to make this subroutine a part of any programming that is to mix external input through SND.

Next: More time! A battery backed-up real-time clock using a brand new ten-year timer from National Semiconductor, plus an interface for the SP0256·AL2 allophone·based speech synthesizer to give a full range of vocabulary and inflection. I'll wrap this series up with a talking clock with battery back·up. (end)

Editor's Note: Dennis has been very busy

these last few weeks on an exciting new project he'll call the Data Gatherer. It's a data

acquisition and control system with, among

other things, 12-bit A/D conversion; 12-bit D/A

conversion; a real-time clock calendar with

battery back-up; a 10-bit parallel port; and

with operating system in ROM. We’re going

to take a break from the Real-Time Talking

clock next month to present Part I of the Data

Gatherer. As soon as Dennis gets the excitement out of his system, we'll go back to

the Talking Clock!

Listings

Program Listing 1. Complete assembly language listing to maintain a software real-time clock with synthesized speech time.
3E00          00100       ORG    $3E00
00110 *
FF52  00120 VOICE EQU    $FF52
00130 *
3E00 1A 50    00140 INTOFF ORCC  #$50   * TURN INTERRUPTS OFF
3E02 BE 3E33  00150        LDX   #START * POINT X TO SERVICE ROUTINE
3E05 BF 0100  00160        STX   $010D  * STORE ROUTINE TO IRQ VECTOR
3E08 86 37    00170        LDA   #$37   * VALUE 00110111 FOR MASKING
3E0A B7 FF03  00180        STA   $FE03  * TURN ON VERTICAL SYNC
3E00 8E FF50  00190        LDX   #VOICE-2 * POINT TO PORT OUPUTS
3E10 4F       00200        CLRA         * PREPARE TO OPEN PORT
3E11 A7 01    00210        STA   1,X    * OPEN PORT A
3E13 86 39    00220        LDA   #$39   * PREPARE DIRECTION
3E15 A7 84    00230        STA   ,X     * SET SPEECH I/O BITS
3E17 86 04    00240        LDA   #$04   * PREPARE TO CLOSE
3E19 A7 01    00250        STA   1,X    * CLOSE PORT A
3E1B 4F       00260        CLRA         * PREPARE TO OPEN B
3E1C A7 03    00270        STA   3,X    * OPEN PORT B
3E1E 4A       00280        DECA         * PREPARE ALL OUTPUTS
3E1F A7 02    00290        STA   2,X    * SET ALL BITS OUTPUT
3E21 86 O4    00300        LDA   #$04   * PREPARE TO CLOSE B
3E23 A7 03    00310        STA   3,X    * CLOSE PORT B
3E25 86 20    00320        LDA   #$20   * PREPARE TO RESET
3E27 A7 84    00330        STA   ,X     * RESET SPEECH DEVICE
3E29 86 38    00340        LDA   #$38   * CLEAR SPEECH BUFFER
3E2B A7 B4    00350        STA   ,X     * SEND CLEARING PULSE
3E2D 4C       00360        INCA         * READY FOR RECEIVING
3E2E A7 84    00370        STA   ,X     * SET RECEIVING PULSE
3E3D 1C EF    00380        ANDCC #$EF   * INTERRUPTS BACK ON
3E32 39       00390        RTS          * AND BACK TO BASIC "OK"
00400 *
3E33 8E 3EA9 00410 START LDX #IMAGE+10 * POINT X TO 1/10 SEC.
3E36 C6 30   00420       LDB #$30      * B BECOMES ASCII OFFSET
3E38 6C 84   00430       INC ,X        * INCREMENT 1/10 SECONDS
3E3A A6 84   00440       LDA ,X        * GET 1/10 SECONDS VALUE
3E3C 81 36   00450       CMPA #$36     * IS 6/10 SECONDS COUNTED?
3E3E 20 3E   00460       BLT OUT       * IF NOT 6/10 SECONDS, OUT
3E40 80 6B   00470       BSR CLKTST    * SEE IF CLOCK TALKING
3E42 80 40   00480       BSR DEC1      * ELSE BAC UP 1 MEM. LOCATION
3E44 81 3A   00490       CMPA #$3A     * IS IT 1 SECOND YET?
3E46 20 36   00500       BLT OUT       * IF NOT 1 SECOND, OUT
3E4B 80 4E   00510       BSR DEC2      * ELSE BACK UP 2 MEM. LOCNS.
3E4A 81 3A   00520       CMPA #$3A     * IS IT 10 SECONDS YET?
3E4C 2D 30   00530       BLT OUT       * IF NOT 10 SECONDS, OUT
3E4E B0 41   00540       BSR DEC1      * BACK UP 1 MEM. LOCATION
3E50 81 36   00550       CMPA #$36     * IS IT 60 SECONDS YET?
3E52 20 2A   00560       BLT OUT       * IF NOT 60 SECONDS, OUT
3E54 80 42   00570       BSR DEC2      * ELSE BACK UP 2 MEM. LOCNS.
3E56 17 0060 00580       LBSR CLOXER
3E59 81 3A   00590       CMPA #$3A     * IS IT 10 MINUTES YET?
3E5B 20 21   00600       BLT OUT       * IF NOT 10 MINUTES, OUT
3E5D 8D 32   00610       BSR DEC1      * ELSE BACK UP 1 MEM. LOCATION
3E5F 81 36   00620       CMPA #$36     * IS IT 60 MINUTES YET?
3E61 20 1B   00630       BLT OUT       * IF NOT 60 MINUTES, OUT
3E63 80 33   00640       BSR DEC2      * ELSE BACK UP 2 MEM. LOCNS.
3E65 81 35   00641       CMPA #$35     * IS IT 5 HOURS?
3E67 26 0D   00642       BNE NOT24     * IF NOT, TEST FOR 10
3E69 A6 1F   00643       LDA -1,X      * GET NEXT LOCATION
3E6B 81 32   00644       CMPA #$32     * IS IT 25 HOURS?
3E6D 26 0F   00645       BNE OUT       * IF NOT, THEN OUT
3E6F E7 1F   00646       STB -1,X      * MAKE U5 HOURS
3E71 5C      00647       INCB          * GET VALUE "1"
3E72 E7 84   00648       STB ,X        * MAKE 01 HOURS
3E74 20 08   00649       BRA OUT       AND BE DONE WITH IT
3E76 B1 3A   00650 NOT24 CMPA #$3A     * IS IT 10 HOURS YET?
3E78 20 04   00660       BLT OUT       * IF NOT 10 HOURS, OUT
3E7A E7 84   00661       STB ,X        * PLACE #$30 (ASCII ZERO)
3E7C 6C 82   00662       INC ,-X       * BACK UP ONE MEM. LOCATION
00710 *
3E7E 108E 0416 00720 OUT LDY #$0416    * POINT TO RIGHT SCREEN
3E82 8E 3E9F 00730       LDX #IMAGE    * POINT X TO CLOCK IMAGE
3E85 C6 0A   00740    LDB #$0A * COUNT 10 SCREEN POSITIONS
3E87 A6 80   00750 LOOP LDA ,X+ * GET CHARACTER FROM CLOCK
3E89 A7 A0   00760    STA ,Y+ * AND PLACE IT ON THE SCREEN
3E8B 5A      00770    DECB    * DONE WITH IMAGE YET?
3E8C 26 F9   00780    BNE LOOP * IF NOT, THEN GET NEXT CHAR.
00790 *
3E8E 7E 894C 00810    JMP $894C * AND T0 8ASIC TO DO RTI
00820 *
3591 E7 84   00830 DEC1 STB ,X * PLACE $30 (ASCII ZERO)
3E93 6C 82   00840    INC ,-X * BACK UP ONE MEM. LOCATION
3E95 A6 84   00850    LDA ,X * GET VALUE FROM IMAGE
3597 39      00860    RTS    * 8ACK T0 MAIN PROGRAM
00870 *
3E98 E7 84   00880 DEC2 STB ,X * PLACE $30 (ASCII ZERO)
3E9A 6C 83   00890    INC ,--X * BACK UP TWO MEM. LOCATIONS
3E9C A6 84   00900    LDA ,X * GET VALUE FROM IMAGE
3E9E 39      00910    RTS    * BACK TO MAIN PROGRAM
00920 *
3E9F 31      00930 IMAGE FCC /11:59:59.00/
31
3A
35
39
3A
35
39
2E
30
30
00940 *
3EAA 00     00950 AMFLAG FCB $00 * 00 = AM, 01 = PM
3EAB 00     00960 WHICH FCB $00 * SAYING WHICH?
3EAC 00     00970 CLKON FCB $00 * 00 = MUTE, 01 = TALKING
00980 *
3EAD 34 02  00990 CLKTST PSHS A    * SAVE A REGISTER
3EAF 86 3EAC 01000    LDA CLKON * GET TALKING STATUS
35B2 27 02   01001    BEQ NOTYET * IF ZERO, NOT TALKING
3EB2 80 03   01000 DOTALK BSR CLOXER * READY T0 SPEAK TIME
35B6 35 02   01050 NOTYET PULS A * WHEN DONE RESTORE A
3EB8 39      01060    RTS * AND BACK TO INT. CLOCK
01070 *
3E89 36 36   01080 CLOXER PSHS A,B,X,Y * SAVE ALL IN SIGHT
3EB8 86 01   01090    LDA #1 * SET CLOCK IN PROGRESS
3EB0 87 3EAC 01100    STA CLKON * AND PUT INT0 FLAG
3EC0 86 FF50 01110 LDA VOICE-2 * POINT TO CONTROL PORT
3EC3 81 58 01120 CMPA #$F8 * AND SEE IF READY
3EC5 27 03 01130 BEQ OKAY * IF $FB, IT'S READY
3EC7 35 36 01140 PULS A,B,X,Y * ELSE RESTORE EVERYTHING
3EC9 39    01150    RTS * AND BACK TO INT. CLOCK
01160 *
3ECA 108E 3ED7 01170 OKAY LDY #SPEAK * POINT TO ROUTINES
3ECE 8E 3595 01180    LDX #IMAGE * POINT TO CLOCK DIGITS
3E01 36 3EA8 01190    LDA WHICH * FIND WHICH ROUTINE
3E04 A8 01200    ASLA * 2-BYTE ADDRESS OFFSET
3E05 65 B6 01210    JMP [A,Y] * AND GO TO THE ROUTINE
01220 *
3ED7 3EDF  01230 SPEAK FDB ITIS * ROUTINE EOR "IT IS"
3E09 3EEA  01240    FDB HOURS * ROUTINE SAYS HOUR
3E08 3532  01250    FDB MINUTE * ROUTINE SAYS MINUTE
3EDD 3F66  01260    FDB AMPM * ROUTINE SAYS "AM" OR "PM"
01270 *
3EDF 86 18 01280 ITIS LDA #24 * CET VOICE VALUE "IT IS"
3EE1 17 0098  01290    LBSR TALKER * AND SPEAK IT
3EE4 7C 3EA8  01300    INC WHICH * POINT TO NEXT ROUTINE
3EE7 35 36   01310    PULS A,B,X,Y * RESTORE ALL REGISTERS
3EE9 39      01320    RTS * BACK TO INT. CLOCK
01330 *
3EEA A6 00   01340 HOURS LDA 0,X * GET FIRST HOUR DIGIT
3EEC 80 30 01350    SUBA #$30 * STRIP ASCII OFESET
3EEE 27 2C 01360    BEQ HOUR0 * GO IF 00 - 09 HOURS
3EF0 4A    01370    DECA    * DECREMENT TO TEST
3EF1 27 00 01380    BEQ HOUR1  * GO IF 10 — 19 HOURS
3E?3 A6 01 01390    LDA 1,X  * ELSE IS 2; GET NEXT
3EF5 80 28 01400    SUBA #40 * NUMBER JUGGLING *****
3EF7 17 0082 01410 LBSR TALKER * AND SPEAK THE VALUE
3EFA 81 0C 01420 CMPA #12 * SEE IF 12 O'CLOCK
3EFC 27 24 01430 BEQ SETAM * IF IT IS, THEN 12 AM
3EFE 20 27 01440 BRA SETPM * ELSE THEN IS PM
3F00 A6 01 01450 HOUR1 LDA 1,X * HOUR IS 1-9; GET NEXT
3F02 80 30 01460 SUBA #$30 * STRIP ASCII OFFSET
3F04 81 02 01470 CMPA #2 * CHECK IF 12 O`CLOCK
3F06 22 0E 01480 BHI NIGHT * IF HIGHER, THEN PM
3F08 27 06 01490 BEQ TWELVE * ELSE IS EXACTLY 12
3F0A 8B 0A 01500 ADDA #10 * ELSE JUGGLE FOR VOICE
3F0C 8D 6E 01510 BSR TALKER * AND SPEAK THE VALUE
3F0E 20 12 01520 BRA SETAM * AND SET THE MORNING
3F10 86 0C 01530 TWELVE LDA #12 * IF 12 THEN GET IT
3F12 8D 68 01540 BSR TALKER * AND SPEAK THE VALUE
3F14 20 11 01550 BRA SETPM * AND SET IT TO BE PM
3F16 80 02 01560 NIGHT SUBA #2 * ELSE IS NIGHT; JUGGLE
3F18 8D 62 01570 BSR TALKER * AND SPEAK THE VALUE
3F1A 20 0B 01580 BRA SETPM * AN0 SET IT AS PM
3F1C A6 01 01590 HOURO LDA 1,X * IF 0-9 HOURS, GET NEXT
3F1E 80 30 01600 SUBA #$30 * STRIP ASCII OFFSET
3F20 8D 5A 01610 BSR TALKER * ANU SPEAK THE VALUE O
3F22 7F 3EAA 01620 SETAM CLR AMFLAG * ROUTINE SETS AM FLAG
3F25 20 05 01630 BRA GOHOUR * AND GOES ON OUT
3F27 86 01 01640 SETPM LDA #1 * ROUTINE SETS PM FLAG
3F29 B7 3EAB 01650 STA AMFLAG * AND PUTS IN PLACE
3F2C 7C 3EAB 01660 GOHOUR INC WHICH * POINT TO NEXT ROUTINE
3F2F 35 36 01670 PULS A,B,X,Y * RESTORE ALL REGISTERS
3F31 39 01680 RTS * BACK T0 INT. CLOCK
01690 *
3F32 A6 03 01700 MINUTE LDA 3,X * GET FIRST MINUTE DIGIT
3F34 80 30 01710 SUBA #$30 * STRIP ASCII OFFSET
3F36 27 19 01720 BEQ MIN1X0 * IF :00 TO :09, GO
3F38 4A 01730 DECA * DECREMENT FOR TEST
3F39 27 0E 01740 BEQ MIN1X1 * IF :10 TO :19, GO
3F3B 8B 13 01750 ADDA #19 * ELSE GET VOICE OFFSET
3F3D 8D 3D 01760 BSR TALKER * SPEAK :20 :30 :40 :50
3F3F A6 04 01770 LDA 4,X * GET LAST MINUTE
3F41 80 30 01780 SUBA #$30 * STRIP ASCII OFFSET
3F43 27 1B 01790 BEQ GOMIN * IF :X0, THEN GO
3F45 8D 35 01800 BSR TALKER * ELSE SPEAK THE MINUTE
3F47 20 17 01810 BRA GOMIN * AND GO ON OUT
3F49 A6 04 01820 MIN1X1 LDA 4,X * IF :10 TO :19, GET NEXT
3F4B 80 26 01830 SUBA #38 * JUGGLE FOR VOICE
3F4D 8D 2D 01840 BSR TALKER * AND SPEAK THE VALUE
3F4F 20 0F 01850 BRA GOMIN * FINALLY GOING OUT
3F51 A6 04 01860 MIN1X0 LDA 4,X * IF :00 TO :09, GET NEXT
3F53 80 30 01870 SUBA #$30 * STRIP ASCII OFFSET
3F55 27 09 01880 BEQ GOMIN * IF :00, GO OUT SILENTLY
3F57 34 02 01890 PSHS A * STASH A VALUE
3F59 4F 01900 CLRA * GET READY A ZERO
3F5A 8D 20 01910 BSR TALKER * AND MAKE IT SAY "OH"
3F5C 35 02 01920 PULS A * RESTORE LAST MINUTE
3F5E 8D 1C 01930 BSR TALKER * AND SPEAK THE MINUTE
3F60 7C 3EAB 01940 GOMIN INC WHICH * POINT TO NEXT ROUTINE
3F63 35 36 01950 PULS A,B,X,Y * RESTORE ALL REGISTERS
3F65 39 01960 RTS * AND BACK TO INT. CLOCK
01970 *
01970 *
3F66 B6 3EAA 01980 AMPM LDA AMFLAG * GET AM—PM FLAG VALUE
3F69 27 04   01990      BEQ MORN   * IF 0, THEN IT'S AM
3F6B 86 1A   02000      LDA #26    * IF 1, THEN GET PM VALUE
3F6D 20 02   02010      BRA GOTIME * AND GO OUT OF ROUTINE
3F6F 86 19   02020 MORN LDA #25    * IF 0, THEN GET AM VALUE
3F71 8D 09   02030 GOTIME BSR TALKER * SPEAK "AM" OR "PM"
3F73 7F 3EAB 02040      CLR WHICH  * CLEAR ROUTINE POINTER
3F76 7F 3EAC 02050      CLR CLKON  * CLEAR CLOCK ON POINTER
3F79 35 36   02060      PULS A,B,X,Y * RESTORE ALL REGISTERS
3F7B 39      02070      RTS        * BACK TO INT. CLOCK
02080 *
3F7C B7 FE52 02090 TALKER STA VOICE * STORE VALUE TO SPEAK
3F7F 34 02   02100      PSHS A     * SAVE THE VALUE
3F81 86 38   02110      LDA #$38   * GET VALUE FOR LOW PULSE
3F83 B7 FF50 02120      STA VOICE-2 * PULSE VOICE TO ACCEPT
3F86 4C      02130      INCA       * GET VALUE FOR HI PULSE
3F87 B7 FF50 02140      STA VOICE-2 * PULSE VOICE TO READY
3F8A 35 02   02150      PULS A     * GET VOICE VALUE BACK
3F8C 39      02160      RTS        * BACK TO TALK ROUTINE
02170 *
3EOO    02180      END INTOFF
00000 TOTAL ERRORS
AMFLAG 3EAA GOHOUR 3F2C ITIS   3EDF NOTYET 3EB6 TWELVE 3F10
AMPM   3F66 GOMIN  3F60 LOOP   3E87 OKAY   3ECA VOICE  FF52
CLKON  3EAC GOTIME 3F71 MIN1X0 3F51 OUT    3E7E WHICH  3EAB
CLKTST 3EAD HOUR0  3F1C MIN1X1 3F49 SETAM  3F22
CLOXER 3EB9 HOUR1  3F00 MINUTE 3F32 SETPM  3F27
DEC1   3E91 HOURS  3EEA MORN   3F6F SPEAK  3ED7
DEC2   3E98 IMAGE  3E9F NIGHT  3F16 START  3E33
DOTALK 3EB4 INTOFF 3E00 NOT24  3E76 TALKER 3F7C
Program Listing 2.
Basic program to set the time for the program in Listing 1.
Once the time is set, this program may be deleted.
1 REM * TIME SETTING PROGRAM
2 REM * FOR INTERRUPT-DRIVEN
3 REM * VOICE CLOCK (ONLY).
4 CLS
S PRINT:PRINT
6 PRINT"ENTER THE TIME IN THE FORMAT:"
7 PRINT"00:00:00.0"
8 PRINT
9 PRINT"NOTE:  USE 24-HOUR TIME."
10 PRINT:PRINT
11 LINEINPUTA$
12 IFLEN(A$)<>10THENRUN
13 FORX=1TO10:A$(X)=MID$(A$,X,1):NEXT
14 IFA$(3)<>":"ORA$(6)<>":"ORA$(9)<>"
."THENRUN
15 Q$=A$(1):GOSUB30
16 Q$=A$(2):GOSUB30
17 Q$=A$(4):GOSUB30
18 Q$=A$(5):GOSUB30
19 Q$=A$(7):GOSUB30
20 Q$=A$(8):GOSUB30
21 Q$=A$(10):GOSUB30
22 FORX=1TO10
23 POKE16030+X,ASC(MID$(A$,X,1))
24 NEXT
25 PRINT:PRINT"TIME SET"
26 FORX=lTO1000:NEXT
27 CLS
28 END
29 STOP
30 IFQ$<"0"ORQ$>"9"THENRUNELSERETURN
Program Listing 3.
A Basic clock program to demonstrate the use of the voice synthesizer.
10 INPUT"HOUR";H
20 INPUT"MINUTE";H
30 A=&HFF50:B=A+1:C=B+1:D=C+1
60 POKEB,0:POKEA,&H39:POKEB,4
50 POKED,0:POKEC,&HFF:POKED,4
60 T=&H38:U=&H39
70 POKEA,&H2O
80 TIMER=0
90 IFTIMER>55THEN100ELSE90
100 S=S+1:IFS=60THENS=O:M=M+1:GOSUB120:IFM=6OTHENM=0:H=H+1:IFH=1
3THENH=1
110 GOTO80
120 POKEC,24
130 GOSUB200
160 POKEC,H:GOSUB200
150 IFM<10THENPOKEC,0:COSUB200
160 IFM<21THENPOKEC,M:GOSUB200:GOTO180
170 IFM>20THENPOKEC,l8+INT(H/10):GOSUB200:IFM-(INT(M/10))*10=0THEN180ELSEPOKEC,M-(INT(M/10))*10:GOSUB200
180 POKEC,26:GOSUB200
190 RETURN
200 POKEA,T:POKEA,U
210 IFPEEK(A)=253THEN210ELSERETURN