quest'n'click 4.5.0 · r1

Built for quest'n'click 4.5.0, revision r1. Whether it is published, and which one is current, is recorded at publication.json.

Voices: per language and per hero

Français · Type: how-to + reference + explanation · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.4.0

A spoken line plays a recorded clip when the game has one for it. This page says how a line finds its clip, how a translated game gets its own recordings, how a line whose words change with the hero is recorded once per value, what plays when a clip is missing, and what the browser and an offline player allow. It ends with the production table (voices), from the lines to the files an actor sends back.

How a line finds its clip

The files live in games/<id>/audio/voice/ in the engine repository, game/audio/voice/ in a created project; npm run assets encodes them (64 kb/s, mono) into public/assets/audio/voices/.

Guarantee · voice-keyed-then-plain · referenced · executed on aff6caab7514 · scope self-checked by the author (AI), not reviewed A line with switches plays its keyed clip for the flags as they stand, else its plain id.

With a flag still unset, it plays the plain id only.

Voice in the content

audio: { voices: { grandma_01: 'grandma-01.mp3' } },   // files in games/<id>/audio/voice/
{ say: ['grandma', 'Pixel! Bad news.'], voice: 'grandma_01' }

Per language: voicesByLang

The game's own language (lang in game.ts, en when absent) plays audio.voices. For any other language the player picks (Translations):

audio.voicesByLang[lang]What plays
absentthe game's own clips, audio.voices: the translated text, voiced in the original language
presentonly its clips, under the same ids (and the same keys); a line it lacks has no voice

Limit · no-per-line-fallback There is no line-by-line fallback between languages: once a language has its own map, a line missing from it is not voiced in the game's language; it shows for its reading time. Record every line of that language, or leave the language without a map.

Guarantee · voices-by-language · referenced · executed on aff6caab7514 · scope self-checked by the author (AI), not reviewed A translated game plays audio.voicesByLang[lang] in place of audio.voices, under the same line ids.

Since 4.4.0 npm run assets ships the clips of voicesByLang too (before, only audio.voices was encoded and a translated page played nothing), and npm run voices -- check reads each language's own text.

Lines with text switches (per hero)

A line whose words follow a flag ({form?m=him|f=her}, the syntax is on Heroes) is recorded once per value: Grandma saying "him" and "her" is two clips. The clip is keyed by the line's id and the values of the flags its switches read:

audio: { voices: {
  grandma_saw: 'grandma_saw.mp3',              // the plain id: the fallback
  'grandma_saw~form=m': 'grandma_saw_m.mp3',   // {form?m=…} with form = "m"
  'grandma_saw~form=f': 'grandma_saw_f.mp3',
  'greet~form=f~mood=calm': 'greet_f_calm.mp3', // two flags in one line: sorted by name, joined by "~"
} }

The Studio's "Heroes × languages" view (4.4.0) shows, for each such line, the text each hero reads in each language, the clip that plays and the keyed clip that is missing: Studio: voices and the heroes × languages matrix.

What the browser allows

Browsers refuse sound before the player's first gesture on the page. The engine waits for the first pointerdown, touchend or keydown; choosing New game or Continue on the title screen is one, so the first room's lines can be heard.

A line the game has no clip for shows for its reading time. A line that has a clip which cannot play ends as soon as the clip gives up, without the reading time:

Limit · voiced-line-without-sound In 4.4.0 a voiced line whose clip cannot play does not fall back to the reading time: the speech bubble closes at once (src/engine/dom/speech.ts). A player who turns the sounds off in a voiced game reads those lines only in a conversation's transcript and the screen reader's live region. Ship every clip you declare.

Offline

With offline: 'full' (the default in game.ts), once the current room and its neighbours are loaded the player fetches every image and sound of the game in the background, in batches, the clips of every language included (voicesByLang, and every keyed clip of a line). The pause menu shows the state ("Offline: complete", or a retry). The background download is skipped on a "save data" or 2G connection. With offline: 'nearby', only the current room and its neighbours are cached, and a voice line of a room never visited needs the network: without it, the clip fails and the line closes at once (see What the browser allows). More: Install as an app.

Producing the recordings

With the production table you get, per language, every clip the game needs, who speaks it, its text in that language, its file and its status, kept in games/<id>/voices.json (game/voices.json in a created project), and a check of every recorded file.

Where to run the commands

ffmpeg (with ffprobe) is required for check: see doctor.

Steps

  1. Give every line a stable id. Without one, a line is not listed.

    # in the engine repository
    npm run ids -- --lines --write
    
    # in the created project
    npx quest-n-click ids --lines --write
    

    Without --write it is a dry run. --lines=all first turns every plain string line into { say: ['hero', text], id }, for a game that voices every line.

  1. Export the table for the actors, per language (CSV, or --json):

    # in the engine repository
    npm run voices -- export --lang fr > voices-fr.csv
    

    npm run voices -- status gives the counts per language: the clips needed, their statuses (draft, record, recorded, approved), how many have a file, and the clips no line claims.

  1. File the clips. Put each file in the voice folder, and name it under its id (or key) in audio.voices, or in audio.voicesByLang.<lang> for a translation. Then encode them:

    # in the engine repository
    npm run assets
    
  1. Bring the statuses back: the CSV (or JSON) the actors filled, with statuses, actors and notes.

    # in the engine repository
    npm run voices -- import voices-fr.csv --lang fr
    

    It refuses a row whose line the game does not have, or an unknown status, and writes nothing then.

  1. Check the files.

    # in the engine repository
    npm run voices -- check
    

    It measures each encoded clip with ffmpeg: the codec (mp3, ogg vorbis or opus, aac, wav, flac), at least 22 050 Hz, at least 300 ms and at most three times the line's reading time, loudness between −23 and −12 LUFS (speech is about −16), a peak below −1 dBFS. --release turns "approved without a clip" from a warning into an error; npx quest-n-click release runs voices check --release.

Expected result, and how to check it

npm run voices -- check ends with ✔ [<id>] voices: 0 error(s), …; in the game, the line plays its clip and stays on screen until the clip ends.

Common errors

SymptomCauseFix
A line is missing from the tableIt has no stable idids --lines --write
<id>: approved without a clipApproved in voices.json, no file in the mapFile the clip, or change its status
<id>: the clip cannot be readThe file is not in public/assets/audio/voices/ yetnpm run assets, then check again
<id>: a clip no line claimsA key no line of that language has (a renamed id, a dropped switch)Rename the key, or remove it
✖ no line "<id>" in the game (import)A row from another version of the gameExport the table again
A French player hears nothingvoicesByLang.fr exists but lacks that lineRecord it (see Per language)
Voiced lines flash by without soundSounds off, voice volume 0, or the clip file missingCheck the pause menu and Settings; npm run assets

Files or data changed

games/<id>/voices.json, games/<id>/audio/voice/, game.ts (audio.voices, audio.voicesByLang), the sources (ids --write adds ids), public/assets/audio/voices/ and assets.gen.json (a translation's clips keyed <lang>:<id>).

Recovery or going back

voices.json, the maps and the sources are in Git: git diff shows what ids --write and import changed, and git checkout -- <file> undoes it. npm run voices -- status and export write nothing.

References


Edit the source of this page · Report an error