Technical appendix · Home · Français
The speech is concatenated recordings, not a synthesised signal: a
single voice, cut into phoneme and transition fragments, is reassembled
from phoneme strings. There is no text-to-phoneme code in the
game: each spoken line was converted in advance and stored. tools/maup_speech.py
ports the runtime, and adds a French front end, written for this
project, so that any text can be spoken.
python3 -I tools/maup_speech.py "Je vous attendais." -o out.wav [--voice male]
python3 -I tools/maup_speech.py --phrase 1 -o out.wav
python3 -I tools/maup_speech.py --phonemes "ZetEEdAA" -o out.wav
| Sound | Source |
|---|---|
| The game's line "J'étais dans ma chambre." | Phrase 1, stored in the game |
| The same line from the French rules | Text, through text_to_phonemes |
| A new sentence, faster voice | Text not in the game |
| The same sentence, slower voice | Text not in the game |
| Part | Status |
|---|---|
Fragment script (compile_words) |
Matches maup.exe's code in an emulator on 449 of 450
phrases; the other is the quirk under "Driver" |
Audio decoding (render_script) |
Same samples as the game's Sound Blaster player in an emulator, on all 450 phrases |
| Sample rate | Read from the code (PIT divisor), not measured |
| Pause lengths | Estimated: the game uses CPU-dependent busy loops |
| French front end | Not from the game: written for this project. 95.2% of phonemes agree with the game's 450 phrases, and phrase lengths correlate at 0.997. Those phrases were also used to tune it, so new text will do worse |
| Real game output | Not compared with a recording of the game running |
Run python3 -I tools/verify_speech.py to repeat the
emulator checks (about 90 s, needs unicorn).
Both records are in G4.CON.
Record 0x2C, phrases. 460 big-endian
offsets relative to 0x398, then words. Phrase n is
class 5 string n: the dialogue. 450 are used. Each word:
| Bits | Meaning |
|---|---|
| 15-12 | duration, the only field the driver uses |
| 11-6 | two 3-bit fields the driver reads but never uses, possibly left over from another version |
| 5-0 | phoneme |
| Codes | Phonemes (tool's ASCII name) |
|---|---|
00-0D |
i, é e, è E, a, (04 unused),
open o O, o, ou u, u y, eu
2, schwa @, in I, an
A, on U |
16-25 |
p b m f v t d n s z l, ch S, j Z, k g
r |
2D-30 |
yod j, w, u of "lui" H, gn
N |
3C, 3D, 3E |
short pause, long pause, hiatus without a pause |
Labels were read off the aligned text; the driver only knows the
numbers. Codes 0E-15 have driver support but
no phrase uses them.
Record 0x2B, samples. Big-endian word
tables, then audio at 0x508:
| Offset | Entries | Used by script op |
|---|---|---|
0x000 |
22 vowel entries, 6 bytes each, with 4 boundaries sharing the next entry's first | 0 |
0x086 |
consonant set 1 | 2 |
0x15A |
consonant set 2 | 6 |
0x22E |
transitions: (vowel + consonant * 14), 2 bytes
each |
4 |
INT 0FEh service 0x1E = EA
0x166ED is called once, from MAUPITI1 EA
0x1191B, with the phrase, the character, and both
records.
0x167D8. A window over
three phonemes (previous, current, next) produces a script of
(op, a, b) triples: which fragment of which table. Phoneme
class comes from the code and table DS:F72;
DS:FA6 and DS:F8C pick variants by
neighbouring vowel.0x1605E, resolving
each triple to a sample range.0x16245), Sound Blaster direct DAC, DSP command
0x10 (EA 0x163C6), and port 0x389
(EA 0x1654B).Decoding. Each byte is two samples, high nibble
first. A sample adds table DS:1212 to the previous one,
modulo 256, starting from 0x80 in every fragment:
-36 -28 -21 -15 -10 -6 -3 -1 0 1 3 6 10 15 21 28.
Voices. There is one set of recordings. The pitch
comes only from the playback rate. The selector table at
MAUPITI1 EA 0x11864 has nine entries, which
appear to be the nine characters in their usual order
(inferred):
| Characters | Selector | PIT divisor | Rate |
|---|---|---|---|
| Maguy, Sue, Anita | above 0 | 0x50 |
14,915 Hz |
| Roy, Bruce, Anton, Chris, Bob, Juste | 0 or below | 0x59 |
13,407 Hz |
With a Sound Blaster, both become 0x6E when the speed
test near EA 0x1738C gives under 12
(inferred: a slow machine).
Quirk. The compile loop compares offsets as signed
numbers, so phrase 368 ("Tu as besoin de moi, biquet?"), which crosses
offset 0x8000, stops after its first phoneme in the
emulated code. It presumably does in the game too. The tool plays the
whole phrase. The port also keeps a fall-through at EA
0x16C74 that looks like a bug: it ignores the following
vowel when choosing a consonant variant.
text_to_phonemes applies an exception list and ordered
letter rules, then liaison, schwa dropping, and the game's habits
(je devoiced before a voiceless consonant, n + yod written
as gn). phonemes_to_words adds durations: long
vowel pairs before a pause, short vowels elsewhere, and the most common
duration for each consonant.
Known limits: French spelling is irregular, so unusual words will be
wrong; verb endings in -ent are only recognised after a
subject pronoun; and there is no intonation, as in the game itself. For
exact pronunciation, use --phonemes.
Previous: Text and fonts · Next: Naming · Technical appendix · Home