| Currah μSpeech | Routines |
| Prev: 0000 | Up: Map | Next: 01AB |
|
Any access to HandleInterrupt (including this opcode fetch) pages the μSpeech ROM in or out. The Spectrum IM1 handler lands here, so this ROM runs first. Every exit jumps back to HandleInterrupt, which pages the Spectrum ROM in and lets IM1 finish.
|
||||
| HandleInterrupt | 0038 | PUSH AF | Preserve AF (this PUSH/POP pair is what Rockfall II checks for). |
|
| 0039 | POP AF | |||
| 003A | DI | Keep further interrupts out while the μSpeech ROM is paged in. | ||
| 003B | PUSH HL | Stash HL, AF, BC and DE on the stack. | ||
| 003C | PUSH AF | |||
| 003D | PUSH BC | |||
| 003E | PUSH DE | |||
|
The speech workspace is a short block at the top of the reserved area, between *RAMTOP and the UDGs (low byte 57). A 48K machine uses FF57; a 16K machine uses 7F57.
|
||||
| 003F | LD HL,($5CB2) | Fetch *RAMTOP. | ||
| 0042 | LD A,L | Jump to ProbeWorkspace if the low byte is not 57. | ||
| 0043 | CP $57 | |||
| 0045 | JR NZ,ProbeWorkspace | |||
| 0047 | LD A,H | Jump to InstallWorkspace if *RAMTOP is still the 48K power-on value (FF57). | ||
| 0048 | CP $FF | |||
| 004A | JR Z,InstallWorkspace | |||
| 004C | CP $7F | Jump to InstallWorkspace if it is still the 16K power-on value (7F57). | ||
| 004E | JR Z,InstallWorkspace | |||
|
*RAMTOP is not a power-on value; probe the speech workspace below it.
|
||||
| ProbeWorkspace | 0050 | LD A,$06 | Call ReadWorkspace for the address of the queue head (index 06). | |
| 0052 | CALL ReadWorkspace | |||
| 0055 | LD B,H | Copy the workspace address to BC. | ||
| 0056 | LD C,L | |||
| 0057 | CALL CheckBufferSpace | Call CheckBufferSpace and jump to InstallWorkspace to reinstall if the workspace is no longer protected by *RAMTOP. | ||
| 005A | JR C,InstallWorkspace | |||
| 005C | CALL ReadBufferPointer | Call ReadBufferPointer. | ||
| 005F | CALL CheckBufferSpace | Call CheckBufferSpace. | ||
| 0062 | CALL C,EmptyQueue | Call EmptyQueue if the buffer pointer is at or below *RAMTOP. | ||
| 0065 | XOR A | Call ReadWorkspace for the flags byte. | ||
| 0066 | CALL ReadWorkspace | |||
| 0069 | BIT 2,A | Jump to ServiceSpeech if this is the second pass of this interrupt. | ||
| 006B | JR NZ,ServiceSpeech | |||
| 006D | SET 2,(HL) | Flag that the next entry is the second pass. | ||
| 006F | POP DE | Restore DE, BC, AF and HL from the stack. | ||
| 0070 | POP BC | |||
| 0071 | POP AF | |||
| 0072 | POP HL | |||
|
Let the Spectrum's IM1 routine (MASK_INT) scan the keyboard first. When it returns, control goes to HandleInterrupt, which pages this ROM back in for the second pass.
|
||||
| ReturnToSpectrum | 0073 | PUSH HL | Leave two copies of HL below the stack. | |
| 0074 | PUSH HL | |||
| 0075 | POP HL | |||
| 0076 | POP HL | |||
| 0077 | LD HL,$0038 | Stack HandleInterrupt over the first copy, as the return address for the Spectrum IM1 routine. | ||
| 007A | PUSH HL | |||
| 007B | DEC SP | Restore HL from the stack. | ||
| 007C | DEC SP | |||
| 007D | POP HL | |||
| 007E | JP HandleInterrupt | Jump to HandleInterrupt to page the Spectrum ROM in. | ||
|
Wait a few frames, then reserve a page below *RAMTOP for the speech workspace.
|
||||
| InstallWorkspace | 0081 | LD A,($5C78) | Jump to ExitInterrupt if *FRAMES is still less than 05. | |
| 0084 | CP $05 | |||
| 0086 | JP C,ExitInterrupt | |||
| 0089 | XOR A | Call ReadWorkspace for the flags byte. | ||
| 008A | CALL ReadWorkspace | |||
| 008D | DEC H | Set *RAMTOP one page below the workspace page. | ||
| 008E | LD ($5CB2),HL | |||
| 0091 | INC H | Write 82 to the flags byte. | ||
| 0092 | LD (HL),$82 | |||
| 0094 | DEC HL | Clear workspace index 01. | ||
| 0095 | XOR A | |||
| 0096 | LD (HL),A | |||
| 0097 | DEC HL | Store the workspace page as the high byte of the buffer pointer (index 02). | ||
| 0098 | LD (HL),H | |||
| 0099 | DEC HL | Write 51 as its low byte (index 03): an empty queue. | ||
| 009A | LD (HL),$51 | |||
| 009C | DEC HL | Clear workspace indexes 04 and 05. | ||
| 009D | LD (HL),A | |||
| 009E | DEC HL | |||
| 009F | LD (HL),A | |||
| 00A0 | LD HL,($5CB2) | Fetch the new *RAMTOP. | ||
|
Plant a sentinel and move the machine stack below the new *RAMTOP.
|
||||
| InstallWorkspace_Stack | 00A3 | LD (HL),$3E | Write a 3E sentinel at the new *RAMTOP. | |
| 00A5 | DEC HL | |||
| 00A6 | LD (HL),A | Write 00 beneath it. | ||
| 00A7 | LD SP,HL | Point SP one byte below the new *RAMTOP. | ||
| 00A8 | DEC HL | Clear the two bytes below the new stack. | ||
| 00A9 | LD (HL),A | |||
| 00AA | DEC HL | |||
| 00AB | LD (HL),A | |||
| 00AC | LD ($5C3D),HL | Write the error stack to *ERR_SP. | ||
| 00AF | LD DE,$12A9 | Set the return address to MAIN_1 in MAIN_EXEC. | ||
| 00B2 | PUSH DE | Stash the return address on the stack. | ||
| 00B3 | EI | Re-enable interrupts before handing over to the Spectrum ROM. | ||
| 00B4 | JP HandleInterrupt | Jump to HandleInterrupt. | ||
|
Second pass: clear the pass flag, then draw the banner once and speak.
|
||||
| ServiceSpeech | 00B7 | RES 2,(HL) | Clear the second-pass flag. | |
| 00B9 | BIT 7,(HL) | Call PrintBanner if the banner has not been drawn yet. | ||
| 00BB | CALL NZ,PrintBanner | |||
|
The Spectrum IM1 routine runs twice per frame (see ReturnToSpectrum), so take one off *FRAMES to compensate for the extra increment.
|
||||
| 00BE | LD HL,($5C78) | Fetch the *FRAMES word. | ||
| 00C1 | LD A,H | Jump to ServiceSpeech_Tick if the low word of *FRAMES is non-zero. | ||
| 00C2 | OR L | |||
| 00C3 | JR NZ,ServiceSpeech_Tick | |||
| 00C5 | LD HL,$5C7A | Decrease the high byte of *FRAMES if the low word is about to wrap. | ||
| 00C8 | DEC (HL) | |||
| ServiceSpeech_Tick | 00C9 | LD HL,($5C78) | Decrease the low word of *FRAMES. | |
| 00CC | DEC HL | |||
| 00CD | LD ($5C78),HL | |||
| 00D0 | LD HL,$5C02 | Point at the keyboard-state timers (04 bytes apart). | ||
| 00D3 | LD DE,$0004 | |||
| 00D6 | CALL IncrementPair | Call IncrementPair. | ||
| 00D9 | DEC HL | Move back to the five-call counters (5C01 and 5C05). | ||
| 00DA | CALL IncrementPair | Call IncrementPair. | ||
|
Programs speak by assigning the BASIC string s$. Look it up and expand it into allophones if it exists.
|
||||
| 00DD | LD A,$53 | Call FindVariable for "s$". | ||
| 00DF | CALL FindVariable | |||
| 00E2 | CALL Z,ParseSpeechString | Call ParseSpeechString if s$ exists. | ||
| 00E5 | CALL TestKeysVariable | Call TestKeysVariable. | ||
| 00E8 | JP Z,FeedAllophone | Jump to FeedAllophone if key speech is disabled. | ||
| 00EB | XOR A | Call ReadWorkspace for the flags byte. | ||
| 00EC | CALL ReadWorkspace | |||
| 00EF | LD A,($5C3B) | Fetch *FLAGS. | ||
| 00F2 | BIT 5,A | Jump to ServiceSpeech_NoKey if no new key was pressed. | ||
| 00F4 | JR Z,ServiceSpeech_NoKey | |||
| 00F6 | BIT 4,(HL) | Jump to FeedAllophone if this key is already being spoken. | ||
| 00F8 | JR NZ,FeedAllophone | |||
| 00FA | JR ServiceSpeech_Key | Jump to ServiceSpeech_Key. | ||
| ServiceSpeech_NoKey | 00FC | RES 4,(HL) | Clear the "key held" flag and jump to FeedAllophone. | |
| 00FE | JR FeedAllophone | |||
|
A new key is in *LAST_K; turn it into allophones.
|
||||
| ServiceSpeech_Key | 0100 | SET 4,(HL) | Set the "key held" flag. | |
| 0102 | LD A,($5C08) | Fetch *LAST_K. | ||
| 0105 | CP $7B | Jump to ServiceSpeech_NotLetter if the code is 7B or higher. | ||
| 0107 | JR NC,ServiceSpeech_NotLetter | |||
| 0109 | CP $61 | Jump to ServiceSpeech_NotLetter if the code is below "a". | ||
| 010B | JR C,ServiceSpeech_NotLetter | |||
| 010D | SUB $20 | Convert a lowercase letter to uppercase and jump to ServiceSpeech_Lookup. | ||
| 010F | JR ServiceSpeech_Lookup | |||
|
Not a letter. Graphics characters (block graphics and UDGs) share key 80; other symbols go through PunctuationKeys.
|
||||
| ServiceSpeech_NotLetter | 0111 | CP $A5 | Jump to ServiceSpeech_Punctuation if the code is A5 or higher. | |
| 0113 | JR NC,ServiceSpeech_Punctuation | |||
| 0115 | CP $80 | Jump to ServiceSpeech_Punctuation if the code is below 80. | ||
| 0117 | JR C,ServiceSpeech_Punctuation | |||
| 0119 | LD A,$80 | Use dictionary key 80 and jump to ServiceSpeech_Lookup. | ||
| 011B | JR ServiceSpeech_Lookup | |||
| ServiceSpeech_Punctuation | 011D | LD HL,$042D | Search PunctuationKeys for this character. | |
| 0120 | LD BC,$000D | |||
| 0123 | CPIR | Jump to ServiceSpeech_Lookup if there is no mapping. | ||
| 0125 | JR NZ,ServiceSpeech_Lookup | |||
| 0127 | LD BC,$000C | Fetch the replacement from PunctuationValues. | ||
| 012A | ADD HL,BC | |||
| 012B | LD A,(HL) | |||
|
Look the key up in AllophoneDictionary.
|
||||
| ServiceSpeech_Lookup | 012C | LD HL,$0447 | Point at AllophoneDictionary. | |
| 012F | LD B,A | Keep the key in B. | ||
| ServiceSpeech_FindKey | 0130 | BIT 7,(HL) | Jump to ServiceSpeech_NextKey if this byte ends an allophone list. | |
| 0132 | JR NZ,ServiceSpeech_NextKey | |||
| 0134 | INC HL | Move to the next dictionary byte. | ||
| 0135 | JR ServiceSpeech_FindKey | Jump to ServiceSpeech_FindKey. | ||
| ServiceSpeech_NextKey | 0137 | INC HL | Move to the next dictionary key. | |
| 0138 | XOR A | Jump to FeedAllophone if the table has ended. | ||
| 0139 | CP (HL) | |||
| 013A | JR Z,FeedAllophone | |||
| 013C | LD A,B | Jump to ServiceSpeech_Copy if this key matches. | ||
| 013D | CP (HL) | |||
| 013E | JR Z,ServiceSpeech_Copy | |||
| 0140 | INC HL | Move to the next dictionary byte. | ||
| 0141 | JR ServiceSpeech_FindKey | Jump to ServiceSpeech_FindKey. | ||
|
Copy the matching allophones into the speech buffer.
|
||||
| ServiceSpeech_Copy | 0143 | INC HL | Move to the first allophone of this entry. | |
| 0144 | CALL TestQueueEmpty | Call TestQueueEmpty and jump to ServiceSpeech_Store if the queue is empty. | ||
| 0147 | JR Z,ServiceSpeech_Store | |||
| 0149 | INC BC | Step the buffer pointer on one byte. | ||
| 014A | LD A,(BC) | Jump to FeedAllophone (leaving the key unspoken) if the last queued allophone does not have bit 7 set. | ||
| 014B | RLA | |||
| 014C | JR NC,FeedAllophone | |||
| 014E | CALL EmptyQueue | Call EmptyQueue to empty the queue so the new key word replaces the previous one. | ||
| ServiceSpeech_Store | 0151 | LD A,(HL) | Queue this allophone in the speech buffer. | |
| 0152 | LD (BC),A | |||
| 0153 | DEC BC | |||
| 0154 | CALL CheckBufferSpace | Call CheckBufferSpace and jump to ServiceSpeech_Flush if the buffer has hit *RAMTOP. | ||
| 0157 | JR C,ServiceSpeech_Flush | |||
| 0159 | BIT 7,(HL) | Jump to ServiceSpeech_Commit if this was the last allophone of the word. | ||
| 015B | JR NZ,ServiceSpeech_Commit | |||
| 015D | INC HL | Move to the next allophone. | ||
| 015E | JR ServiceSpeech_Store | Jump to ServiceSpeech_Store. | ||
| ServiceSpeech_Flush | 0160 | CALL ReadBufferPointer | Call ReadBufferPointer. | |
| ServiceSpeech_Commit | 0163 | CALL WriteBufferPointer | Call WriteBufferPointer. | |
|
The SP0256 is the speech-synthesiser chip. Read its status at 1000 (bit 0 is the inverted load request) and send the next queued allophone if the input buffer can take one.
|
||||
| FeedAllophone | 0166 | LD A,($1000) | Read the SP0256 status from 1000. | |
| 0169 | BIT 0,A | Jump to ExitInterrupt if the chip's input buffer is full. | ||
| 016B | JR NZ,ExitInterrupt | |||
|
The manual notes that "the unit will remain silent until the first ENTER is pressed after a power up, a NEW, or after a 'report' has been printed at the bottom of the screen." On every interrupt that reaches this code where bit 7 of *ERR_NR is reset (an error code is waiting), this code empties the speech queue through EmptyQueue, so nothing is spoken until the ROM sets *ERR_NR back to FF; that happens when ENTER is pressed and on the editor's error paths (ED_ERROR at 1085 and ED_FULL at 1172 in the Spectrum ROM). The power-on and NEW cases are from the manual (chapter 2 also says any report other than OK has this effect).
|
||||
| 016D | LD A,($5C3A) | Empty the speech queue (EmptyQueue) if BASIC is reporting an error (bit 7 of *ERR_NR reset). | ||
| 0170 | RLA | |||
| 0171 | CALL NC,EmptyQueue | |||
| 0174 | CALL TestQueueEmpty | Call TestQueueEmpty and jump to FeedAllophone_Write if anything is queued. | ||
| 0177 | JR NZ,FeedAllophone_Write | |||
| 0179 | XOR A | Keep the chip fed with PA1 (silence) and jump to ExitInterrupt. | ||
| 017A | LD ($1000),A | |||
| 017D | JR ExitInterrupt | |||
|
The SP0256 can take an allophone; write the next queued one.
|
||||
| FeedAllophone_Write | 017F | LD A,$06 | Call ReadWorkspace for the next allophone. | |
| 0181 | CALL ReadWorkspace | |||
| 0184 | LD DE,$3000 | Point at 3000 (low pitch) or 3001 (high pitch) by bit 6. | ||
| 0187 | BIT 6,A | |||
| 0189 | JR Z,FeedAllophone_Port | |||
| 018B | INC DE | |||
| FeedAllophone_Port | 018C | LD (DE),A | Select the pitch (only the address matters; it switches the speech chip's clock between about 3.05 MHz and 3.26 MHz (MAME's values; see Intonation)). | |
| 018D | LD ($1000),A | Write the allophone to the speech port (the chip ignores bits 6 and 7). | ||
| 0190 | OR A | Work out the queue length (including the allophone just sent). | ||
| 0191 | SBC HL,BC | |||
| 0193 | INC BC | Step the queue pointer on one byte. | ||
| 0194 | PUSH HL | Stash the queue length on the stack. | ||
| 0195 | CALL WriteBufferPointer | Call WriteBufferPointer. | ||
| 0198 | LD A,$06 | Call ReadWorkspace for the queue start. | ||
| 019A | CALL ReadWorkspace | |||
| 019D | LD D,H | Point the copy at the previous queue byte. | ||
| 019E | LD E,L | |||
| 019F | DEC HL | |||
| 01A0 | POP BC | Restore the queue length from the stack. | ||
| 01A1 | LDDR | Move the rest of the queue up one byte. | ||
|
Restore the Spectrum registers and page its ROM back in.
|
||||
| ExitInterrupt | 01A3 | POP DE | Restore DE, BC, AF and HL from the stack. | |
| 01A4 | POP BC | |||
| 01A5 | POP AF | |||
| 01A6 | POP HL | |||
| 01A7 | EI | Re-enable interrupts before handing over to the Spectrum ROM. | ||
| 01A8 | JP HandleInterrupt | Jump to HandleInterrupt. | ||
| Prev: 0000 | Up: Map | Next: 01AB |