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 0800 to 0FFF (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
00-37 Banner bitmaps
38-03AE Interrupt and helpers
03AF-075A Keys, suffixes and dictionary
075B-0792 Banner address list
0793-0797 "Anson"
0798-07FF More banner bitmaps
0800-0FFF Mirror of this ROM
1000 SP0256 allophone / status
3000 / 3001 Low / high intonation
On the first few frames InstallWorkspace lowers *RAMTOP by one page and plants the workspace (low byte 57). 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 1000-1FFF mirror 1000 for status reads and allophone writes (IN / OUT included); 2000 is not mirrored. 3000 / 3001 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). 1000-3FFF respond only while this ROM is paged in. Of the bits returned by a 1000 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 00 as the byte at low address 57, index 01 as 56, and so on. A 48K machine uses FF57; a 16K machine uses 7F57. InstallWorkspace lowers *RAMTOP by one page so BASIC stays out of this block.
Index Address Purpose
00 57 Flags
01 56 Cleared on install
03 54 Live buffer pointer (low, then high)
06 51 Saved queue start / first allophone
The live pointer is a word, so its high byte sits at 55 (install first writes the workspace page there). The allophone buffer begins at the address of index 06 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 00:
Bit Meaning
1 Key speech (set at install, and when keys is 01)
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.
1000 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 00 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 3000 (low pitch) or 3001 (high pitch). Bit 6 of the queued byte chooses the port; FeedAllophone_Write then writes the ID to 1000 (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 00DD looks that name up with FindVariable (name code 53) 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 (01) 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 016D.
*RAMTOP drops by 256 bytes on install (to FE57 on 48K). The manual says do not CLEAR above FF50 (48K) or 7E57 (16K). The re-install check at ProbeWorkspace (0050-005A) only goes off when *RAMTOP is at or above the workspace base (FF51 on 48K, 7F51 on 16K), so the code itself accepts CLEAR up to 7F50 on 16K; the manual's 16K figure is just the *RAMTOP install sets and is stricter than the code needs, while its 48K figure FF50 matches the code. A CLEAR into the header forces a re-install with a new banner (chapter 5); that is the check at ProbeWorkspace / 0057. A buffer only 200 bytes long holds over 30 seconds (chapter 2); the default buffer is FA 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 80-A4 (block graphics and UDGs) all use key 80. Everything else is offered to PunctuationKeys first.
ServiceSpeech_Punctuation searches the 0D 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 (08)
Cursor down Cursor (08)
Cursor up Cursor (08)
SGN SIN
AND "&"
All brackets therefore share the "(" entry (spoken as a bracket). The extra cursor keys share the spoken "cursor" for 08. 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 (80) so the scanner's "end of the previous list" step lands on the first real key, and it ends on a key of 00.
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 21) and must appear in the first 18 entries of CharacterKeys. Those values are summed; the sum is the lookup key.
Most sums are searched for in the 36 bytes from CharacterKeys, and the matching allophone is 36 bytes later, in AllophoneValues. Five sums are handled separately and go through the suffix tables instead:
Sum Letter used
64 Last letter (e.g. "(RR)")
68 Last letter (e.g. "(OY)")
5C Last letter (e.g. "(NN)")
66 Last letter (e.g. "(OW)")
78 Letter before last (e.g. "(EAR)")
ParseAllophoneGroup_Suffix takes that letter, normalises it, and searches SuffixKeys. The parallel byte 09 later in SuffixValues is the allophone. A letter that is not in SuffixKeys makes the group invalid.
Suffix letter Allophone
"R" 0E (RR1)
"U" 16 (UW1)
"T" 0D (TT2)
"Y" 05 (OY)
"H" 1D (TH)
"N" 38 (NN2)
"A" 3C (YR)
"E" 2F (XR)
"W" 20 (AW)
Intonation
A write to 3000 or 3001 changes the SP0256 oscillator clock, not the volume. In MAME uspeech.cpp 3000 sets 3,050,000 Hz and 3001 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 1000 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." 0184-018D 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 (FF57-FF51; the last is the first queue byte):
Address Decimal Purpose
FF57 65367 Flags
FF56 65366 Spare
FF55 / FF54 65365 / 65364 Buffer pointer (high, then low)
FF53 / FF52 65363 / 65362 Spare
FF51 65361 Next allophone / queue start
Buffer bytes are laid out as: bit 7 = set on the last allophone of a key-press word (014A 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 (57 flags, 54/55 pointer, 51 queue start) and the LDDR shuffle at 01A1 ("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 39 with F1 (POP AF; the Spectrum ROM has E5, PUSH HL, there). The test works because HandleInterrupt / 39 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.