Examples · AR/VR & Game Developer
Voice lines for your game, from a spreadsheet
Write your characters' lines in a spreadsheet, and one program turns every row into a WAV named after its id, ready to drop into Unity.
Intermediate · 40 min
The idea
A game has dozens of lines. For three, the Studio is enough; for fifty, write them in a spreadsheet, save it as CSV, and let a program make one file per line. Each file is named after the line's id, so the game finds it.
What you need
A key and the openai library, as in Your first API call in Python, and your lines in lines.csv. A spreadsheet saved as CSV works:
id,character,voice,text
inn_welcome,Innkeeper,amy,"Welcome to the Black Cat, traveller! A room is five silver coins a night."
inn_room,Innkeeper,amy,Your room is at the top of the stairs. Mind the cat.
guard_stop,Guard,ryan,Stop right there! Nobody enters the castle after sunset.
guard_pass,Guard,ryan,"The captain is expecting you. Go on, and keep your sword in its sheath."
villain_threat,Morvan,ryan,You came all this way for nothing. My army will burn this valley to ashes!
The voices in English are amy and ryan; characters can share one. The others are paola and riccardo for Italian, thorsten for German and siwis for French: write each line in the language of its voice.
The program
Save it as make_voices.py, next to lines.csv:
import csv
import os
from pathlib import Path
import openai
from openai import OpenAI
client = OpenAI(
base_url="https://api.ai.itsincom.org/v1",
api_key=os.environ["API_KEY"],
max_retries=5,
)
out = Path("voices")
out.mkdir(exist_ok=True)
with open("lines.csv", newline="", encoding="utf-8-sig") as f:
for row in csv.DictReader(f):
path = out / f"{row['id']}.wav"
if path.exists():
continue # already made: delete the file to get a new take
try:
speech = client.audio.speech.create(
model="tts-1", voice=row["voice"], input=row["text"], response_format="wav",
)
except openai.BadRequestError as e:
print(f"{row['id']}: not made. {e.body['message']}")
continue
speech.write_to_file(path)
print(f"{path} {row['character']}")
print("Done: the lines are in the voices folder.")
Run it with python3 make_voices.py.
What you should see
voices/inn_welcome.wav Innkeeper
voices/inn_room.wav Innkeeper
voices/guard_stop.wav Guard
voices/guard_pass.wav Guard
voices/villain_threat.wav Morvan
Done: the lines are in the voices folder.
Five lines take a few seconds. Run the program again and only Done appears: every file is already there. Delete a file to get a new take of that line.
A row with a mistake, like the voice Amy with a capital letter, is skipped: the program prints why, with the list of valid voices (for now the message is in Italian), and goes on with the next row. Fix the row and run the program again: only the missing files are made.
Listen to them
When we transcribed our five files back, four said what was written, one with “Traveler” spelled the American way. Morvan's line came back as “My army will burn this valley's ashes”: maybe the voice swallowed the “to”, maybe the transcription misheard it. That is the line to listen to first.
In Unity
- Drag the
voicesfolder into the Project window, underAssets: Unity imports every WAV as an audio clip. - To play a line, drag its clip into the AudioClip field of an Audio Source, or into a field of your script.
- To load a line by its id from code, put the folder in
Assets/Resources/voicesand writeResources.Load<AudioClip>("voices/inn_welcome"): the path starts insideResources, without.wav. - Keep WAV: Unity compresses the clips when it builds the game.
The program runs on your computer, before you build the game, so the key never goes into the game. Never put your key in a script or a file of a game: whoever has the build can read it, and spend your credits.
Good to know
- Every take is different: the same line comes out a little different each time. The program never overwrites a file, so the takes you like stay.
- Short exclamations are the weak spot: “Halt!” on its own can come out unclear. Listen to the short lines first, or give them a few more words.
- No speed, pitch or emotion control:
speedis accepted and ignored. Six voices are all there is. - The lines are checked before they are read. Villains' threats, battle cries and fantasy violence are fine: Morvan passed. Sexual content, hate and insults to real people are refused, and the program prints the reason and goes on.
- CSV from Excel: save as “CSV UTF-8”. With Italian settings Excel separates the columns with
;: then writecsv.DictReader(f, delimiter=";"). - A whole class at the same time: when too many requests arrive together, the platform asks to wait. The library waits and tries again by itself, up to five times (
max_retries=5).
Exercise
- Write the lines of your own game: a shopkeeper, a quest giver, a boss. Change a line, delete its file and run the program again.
- Find the lines to listen to first: transcribe every file back, as in Text to speech in Python, and print the ones whose words differ from the text.
- Make the Italian version: the same ids with
paolaandriccardo, invoices/it/. In Unity, choose the folder from the language of the game. - Ask for
response_format="mp3"and save.mp3files: how much smaller is the folder? Unity imports MP3 too.
