Speech

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

Samples

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

How far each part is checked

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).

Data

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

Driver

INT 0FEh service 0x1E = EA 0x166ED is called once, from MAUPITI1 EA 0x1191B, with the phrase, the character, and both records.

  1. Compile, EA 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.
  2. Read the script, EA 0x1605E, resolving each triple to a sample range.
  3. Play. Timer 0 is reprogrammed to the sample rate, with one sample per interrupt. Three outputs: PC speaker PWM (EA 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.

The French front end

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