Prev: 0000 Up: Map Next: 01AB
0038: Handle Interrupt
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
Install abandons the interrupted program: MASK_INT returns to MAIN_1, re-entering the BASIC main loop with a fresh stack.
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
Likewise add one to the repeat and delay counters in each half of *KSTATE, to compensate for KEYBOARD's extra decrement. (When a set is free these counters just drift upwards; KEYBOARD reloads them when a new key arrives.)
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