Theory Of Operation
The Currah μSpeech is a 2K ROM that occupies the same addresses as the Spectrum ROM. Any access to HandleInterrupt (including the opcode fetch that lands there) pages one or the other in. The Spectrum IM1 handler always jumps to HandleInterrupt, so this ROM runs first on every interrupt; every exit jumps back to HandleInterrupt to page the Spectrum ROM in and let IM1 finish.
The ROM is mirrored from 2048 to 4095 (per Fuse patch #142). Speech itself is produced by the SP0256 chip, fed one allophone at a time from a short workspace at the top of the reserved area, between *RAMTOP and the UDGs.
Range Contents
0-55 Banner bitmaps
56-942 Interrupt and helpers
943-1882 Keys, suffixes and dictionary
1883-1938 Banner address list
1939-1943 "Anson"
1944-2047 More banner bitmaps
2048-4095 Mirror of this ROM
4096 SP0256 allophone / status
12288 / 12289 Low / high intonation
On the first few frames InstallWorkspace lowers *RAMTOP by one page and plants the workspace (low byte 87). After that, each interrupt at ServiceSpeech:
Key voices map letters, tokens and symbols to allophone IDs through AllophoneDictionary. The s$ path uses CharacterKeys / AllophoneNameSums / AllophoneValues instead. A parenthesised name such as "(SH)" picks an allophone directly (see ParseAllophoneGroup). The queued IDs sit in the workspace buffer and grow downwards; when the SP0256 is ready, FeedAllophone writes one ID and shuffles the rest up (towards the queue head).
Any access to HandleInterrupt toggles the ROM in or out: opcode fetch, memory read or write, and IN / OUT. Addresses in 4096-8191 mirror 4096 for status reads and allophone writes (IN / OUT included); 8192 is not mirrored. 12288 / 12289 decode as 0011xxxx xxxxxxx0 (low) and 0011xxxx xxxxxxx1 (high); only writes or OUT work there. Only the low 6 bits reach the chip (AL2 A1-A6; A7/A8 tied low). 4096-16383 respond only while this ROM is paged in. Of the bits returned by a 4096 read, only bit 0 (busy / inverted load request) has a known meaning. See maziac's findings and MAME uspeech.cpp.
Speech Workspace
The workspace is a few bytes in the page of *P_RAMT. ReadWorkspace reads index 0 as the byte at low address 87, index 1 as 86, and so on. A 48K machine uses 65367; a 16K machine uses 32599. InstallWorkspace lowers *RAMTOP by one page so BASIC stays out of this block.
Index Address Purpose
0 87 Flags
1 86 Cleared on install
3 84 Live buffer pointer (low, then high)
6 81 Saved queue start / first allophone
The live pointer is a word, so its high byte sits at 85 (install first writes the workspace page there). The allophone buffer begins at the address of index 6 and grows down towards the new *RAMTOP. CheckBufferSpace sets carry when the pointer has run out of that reserved page (at or below *RAMTOP).
Flags at index 0:
Bit Meaning
1 Key speech (set at install, and when keys is 1)
2 Second pass (set before chaining to the Spectrum IM1 routine, reset when this ROM is re-entered)
4 A key is already being spoken
6 High intonation (set for codes below "a", reset for codes at or above "a")
7 Banner not yet drawn
Manual chapter 6 instead lists bit 7 as the copyright/banner flag (reset after the message), bit 1 as key voice (set at first), bits 4 and 2 as used in the keyboard scan, and bits 6, 5, 3 and 0 as spare. The meanings above for bits 6 and 2 come from the code.
SP0256 Speech Synthesiser
The μSpeech speaks through a General Instrument SP0256-AL2 chip. FeedAllophone feeds it one allophone at a time from the workspace queue.
4096 is the allophone port. Reading it returns status; bit 0 is the inverted load request (set while the chip's input buffer is full). Writing an allophone ID starts that sound; writing 0 queues PA1 (nominally 10 ms), which stops the chip repeating the tail of its last allophone (see maziac's Allophone loop finding, and CPCWiki's SP0256 Allophones page, which advises sending PA1 after the last allophone to mute it). That repeat behaviour is why a key voice left on during LOAD can give a continuous "e", and why BEEP can draw out an allophone (manual Appendix III).
Intonation is selected by writing to 12288 (low pitch) or 12289 (high pitch). Bit 6 of the queued byte chooses the port; FeedAllophone_Write then writes the ID to 4096 (the chip ignores bits 6 and 7).
The IDs themselves are the AL2 allophone set stored in AllophoneDictionary. Hear every ID on the Allophones page.
BASIC Speech String
Programs speak by assigning the BASIC string variable s$. On each interrupt 221 looks that name up with FindVariable (name code 83) and, if it exists, calls ParseSpeechString. A program must follow LET s$= with PAUSE 1 so the interrupt sees each new s$ (manual chapter 2).
ParseSpeechString ignores an empty string, and also ignores a string whose first character is "*" or "?". Those two markers mean the text has already been spoken, or could not be parsed.
Anything else is expanded into the allophone buffer. A full stop queues two PA5 pauses (the longest). A "(" starts a parenthesised allophone name (see ParseAllophoneGroup). Bracketed groups take their intonation from their last character (for (aer) and (ear), the middle letter): (oUu) is flat, (ouU) goes up (chapter 4); this matches NormaliseCharacter being applied to every character. Other characters are mapped through CharacterKeys.
After a successful pass the first character of s$ is overwritten with "*". If a character cannot be mapped, the first character and the character after the error (or the last character) are overwritten with "?" instead. Either way the next interrupt will skip the string until a program assigns s$ again.
The separate numeric variable keys (1) enables speaking each key press from *LAST_K; that path does not use s$. The keys variable survives NEW. Key voices stay silent until the first ENTER after power-up, after NEW, or after a report; the manual says 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." Every BASIC keyword and printable character is voiced except "~"; all graphics characters are spoken as "graphic". User speech takes priority over key voices. Any report other than OK empties the buffer (chapter 2); that is the *ERR_NR test at 365.
*RAMTOP drops by 256 bytes on install (to 65111 on 48K). The manual says do not CLEAR above 65360 (48K) or 32343 (16K). The re-install check at ProbeWorkspace (0080-0090) only goes off when *RAMTOP is at or above the workspace base (65361 on 48K, 32593 on 16K), so the code itself accepts CLEAR up to 32592 on 16K; the manual's 16K figure is just the *RAMTOP install sets and is stricter than the code needs, while its 48K figure 65360 matches the code. A CLEAR into the header forces a re-install with a new banner (chapter 5); that is the check at ProbeWorkspace / 87. A buffer only 200 bytes long holds over 30 seconds (chapter 2); the default buffer is 250 bytes.
Sources: manual transcription PDF and the archive.org OCR.
The Try s$ page runs this parser in the browser and plays the resulting SP0256 allophones.
Key Speech Remap
When a new key arrives in *LAST_K, ServiceSpeech_Key turns it into a dictionary key and looks it up in AllophoneDictionary. Letters are uppercased, and codes 128-164 (block graphics and UDGs) all use key 128. Everything else is offered to PunctuationKeys first.
ServiceSpeech_Punctuation searches the 13 keys at PunctuationKeys. A miss keeps the original code. A hit takes the byte the same distance into PunctuationValues and uses that as the dictionary key instead.
The table covers symbols and tokens that either share a spoken word with another key, or have no entry of their own in AllophoneDictionary (POINT, CLS, SGN and AND).
LAST_K Dictionary key
")" "("
"[" "("
"]" "("
"{" "("
"}" "("
POINT "."
CLS CLEAR
"@" AT
Cursor right Cursor (8)
Cursor down Cursor (8)
Cursor up Cursor (8)
SGN SIN
AND "&"
All brackets therefore share the "(" entry (spoken as a bracket). The extra cursor keys share the spoken "cursor" for 8. POINT is spoken as a full stop, CLS as CLEAR, "@" as AT, SGN as SIN, and AND as "&".
Allophone Dictionary
AllophoneDictionary is the character-to-allophone table used by ServiceSpeech_Lookup. Each entry is a key byte followed by one or more SP0256-AL2 allophone IDs. Bit 6 of an ID selects high intonation; bit 7 marks the last ID of that entry.
The table starts with a dummy terminator (128) so the scanner's "end of the previous list" step lands on the first real key, and it ends on a key of 0.
Each comment line on AllophoneDictionary has an audio player for that spoken list.
A key that is not in the table is skipped; the interrupt then continues to FeedAllophone. That is why POINT, CLS, SGN and AND are rewritten by PunctuationKeys before the lookup.
Allophone Name Suffixes
A parenthesised name in s$ such as "(AA)" or "(SH)" is read by ParseAllophoneGroup. Each letter is normalised by NormaliseCharacter (so "A" and "a" both become 33) and must appear in the first 24 entries of CharacterKeys. Those values are summed; the sum is the lookup key.
Most sums are searched for in the 54 bytes from CharacterKeys, and the matching allophone is 54 bytes later, in AllophoneValues. Five sums are handled separately and go through the suffix tables instead:
Sum Letter used
100 Last letter (e.g. "(RR)")
104 Last letter (e.g. "(OY)")
92 Last letter (e.g. "(NN)")
102 Last letter (e.g. "(OW)")
120 Letter before last (e.g. "(EAR)")
ParseAllophoneGroup_Suffix takes that letter, normalises it, and searches SuffixKeys. The parallel byte 9 later in SuffixValues is the allophone. A letter that is not in SuffixKeys makes the group invalid.
Suffix letter Allophone
"R" 14 (RR1)
"U" 22 (UW1)
"T" 13 (TT2)
"Y" 5 (OY)
"H" 29 (TH)
"N" 56 (NN2)
"A" 60 (YR)
"E" 47 (XR)
"W" 32 (AW)
Intonation
A write to 12288 or 12289 changes the SP0256 oscillator clock, not the volume. In MAME uspeech.cpp 12288 sets 3,050,000 Hz and 12289 sets 3,260,000 Hz ("oscillator frequency read from hardware").
maziac measured real hardware: the high setting is about 7% higher in pitch and the volume does not change; the new frequency takes about 0.05-0.5 s to settle; the data byte written is ignored (only the address matters); and bit 6 of the byte written to 4096 has no effect on the chip.
The manual says the pitch is "ramped" so that the change is smooth (chapter 4). The datasheet's nominal crystal is 3.12 MHz; Kio's simulation of the Currah oscillator gives about 3.05 MHz (see maziac), so this unit should not be described as running at 3.12 MHz. On k1.spdns.de Kio writes: "Address bit A0 set's [sic] intonation to low frequency (A0=0: ~3MHz clock / ~10kHz samples) or high frequency (A0=1: approx. 7% higher). The shift in frequency is soft and takes around 0.05 to 0.5 seconds, the timing seems to vary (Thomas Busse). The data value written is not used." 388-397 select the port from bit 6 of the queued byte.
Machine-Code API
Manual chapter 6 calls it a "six-byte header" but lists seven addresses (65367-65361; the last is the first queue byte):
Address Decimal Purpose
65367 65367 Flags
65366 65366 Spare
65365 / 65364 65365 / 65364 Buffer pointer (high, then low)
65363 / 65362 65363 / 65362 Spare
65361 65361 Next allophone / queue start
Buffer bytes are laid out as: bit 7 = set on the last allophone of a key-press word (330 tests it; s$ bytes leave it clear), bit 6 = intonation up, bits 0-5 = allophone. A worked example is "he(ll)(oo)" = 27, 7, 62, 53, with the "e" intoned up as 71. This matches the workspace indexes in Speech Workspace (87 flags, 84/85 pointer, 81 queue start) and the LDDR shuffle at 417 ("the entire contents of the buffer is moved up by one"). 16K addresses are the 48K ones minus 32768.
History
Made by Currah Computor Components Ltd, Hartlepool (the original manual spells it "Computor"; Spectrum Computing and the transcription often spell it "Computer"). Addresses in the original printing: Hollymount, Wooler Road, Hartlepool, Cleveland TS26 0HA; the transcription gives Graythrop [sic] (Graythorp) Industrial Estate, Hartlepool TS25 2DF, so the two printings differ.
Released in 1983 at £29.95 (Spectrum Computing). dk'tronics took over Currah in 1985 and kept making the unit; many dk'tronics titles supported it (for example Maziacs and Zig Zag). See Wikipedia: Currah and k1.spdns.de. A "Currah microSpeech Dictionary" was advertised at £3.50 (Home Computing Weekly #90, 27 November 1984, p.5).
The string at ProgrammerCredit is "Anson"; Mark Anson is named as author on the 1983 programming manual title page (archive.org). That he also wrote the ROM is an inference from the string; no source says so outright. The known ROM dump is 2,048 bytes, CRC32 ce7cf52e (same dump in MAME and Fuse).
Hardware
Inside: a 2K ROM, the SP0256-AL2 and a ULA. The case clips together with no screws. The board maziac opened has pin 12 (SER OUT) of the SP0256 removed.
Leads: a UHF lead with a phono plug, a 3.5 mm line lead into the Spectrum's MIC socket, and a UHF trimmer screw at the top right. Speech and the Spectrum's own sound are mixed into the UHF signal, so they come out of the TV speaker. The line lead can also be used as a high-impedance output to a tape recorder or amplifier (Appendix II). Mixing sound into the UHF signal can cause colour "patterning" (Appendix VI). It works behind Interface 1 (Appendix IV).
Sources: the manual and maziac.
Supporting Software
Spectrum Computing lists 86 entries (about 83 distinct titles, as of October 2026). Verified there include Lunar Jetman, Booty, Rockfall, Rockfall II, Maziacs, Zig Zag, Spectrum Voice Chess, Terrahawks, Max Headroom and Twin Kingdom Valley. The list includes modern homebrew, so filter by year for period-only titles. Rockfall II detects the unit by toggling paging and comparing 57 with 241 (POP AF; the Spectrum ROM has 229, PUSH HL, there). The test works because HandleInterrupt / 57 here are PUSH AF / POP AF.
Related Machines
Currah Speech 64 for the Commodore 64 (1984) is a cartridge with an SP0256-AL2; in the USA it was sold as "Voice Messenger" for $49.95 by Currah Technology, Boston (C64-Wiki, Centre for Computing History). Other Spectrum SP0256 units include the Cheetah Sweet Talker, the dk'tronics Speech Synthesiser (which Fuse patch #142 describes as "probably a rebranded μSpeech") and the Fuller Orator. That patch (closed-accepted; milestone v1.7.0; created 2007-08-29 by Stuart Brady) is the Fuse uspeech support.