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 |
- Compensates for the second IM1 pass: one *FRAMES increment and one *KSTATE counter decrement per frame
- Expands the BASIC string s$ if it exists
- Speaks a new key from *LAST_K if key speech is enabled
- Sends the next queued allophone if the SP0256 can take one
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).
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.
The
Try s$ page runs this parser in the browser
and plays the resulting
SP0256 allophones.
Key Speech Remap
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
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.
- Control keys: EDIT, ENTER, cursor, DELETE
- Colour tokens, graphics and symbols, plus LET and NEW
- Digits 30-39
- "#", "$" and "%"
- Letters A-Z
- BASIC tokens from RND through COPY
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)") |
| 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).
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.