How to add TTS to the intro of Griffon


It’s just an overview.

Find the Text

In Griffon’s case, the text is in the code

So just search for “Ever since I was a child”, and there it is, in cutscenes.cpp

const char *story[48] = {
	"The Griffon Legend",
	"Programming/Graphics: Daniel Kennedy",
	"Music/Sound effects: David Turner",
	"Porting to GCW-Zero: Dmitry Smagin",
	"Ever since I was a child",
	"I remember being told the",
	"Legend of the Griffon Knights,",

Pass to Text To Speech Manager

Then, we would like to pass the text to text to speech manager

To call for TTS manager, first, include this in the header

#include "common/text-to-speech.h"

Then initialize TTS manager like this

Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();

Before passing the text to it, we check to see if it is null pointer, to avoid a crash

if (ttsMan != nullptr) 
	ttsMan->say("The Griffon Legend");

We also would like to check if TTS has been enabled already

(This works given that there are proper adjustments to detection.cpp about “tts_enabed”)

#include "common/config-manager.h"

if (ttsMan != nullptr && ConfMan.getBool("tts_enabled"))	
	ttsMan->say("The Griffon Legend");

Obviously we would like the whole story to have TTS, so we loop through

for (int i = 0; i < ARRAYSIZE(story); i++) {
 	// Do the TTS thing

Staying in pace

We would like to have the text shown on the screen be in pace with the TTS speaker.

Therefore, we would like to use the same for loop that is used for showing the text on the screen

See this in the same file, under void GriffonEngine::intro(), there is a do {

for (int i = 0; i < ARRAYSIZE(story); i++) {           // go through the story array

 	int yy = y + i * 10;                               // calculate position

	if (yy > -8 && yy < 240) {
		int x = 160 - strlen(story[i]) * 4;            // when time comes
		drawString(_videoBuffer, story[i], x, yy, 4);  // draw text to the screen

  	if (yy < 10 && i == ARRAYSIZE(story) - 1) {        // last paragraph has left the screen
		return;                                        // proceed to game

Because there are multiple lines appearing on the screen at once, we need a way to keep track of what has been narrated, and what hasn’t yet

int nextparagraph = 0;               //this is new

for (int i = 0; i < ARRAYSIZE(story); i++) {

    int yy = y + i * 10;                           

    // if (i == nextparagraph)     
        // do Text To Speech
        // update nextparagraph to reflect progress

    if (yy > -8 && yy < 240) {
	int x = 160 - strlen(story[i]) * 4;
 	drawString(_videoBuffer, story[i], x, yy, 4);  

    if (yy < 10 && i == ARRAYSIZE(story) - 1) {        

There are also two more issues to be addressed:

  1. It’s better to glue sentences that belong to the same paragraph together. The narrating sounds more natural
  2. Queue new paragraphs behind the current paragraph that is being spoken

We ended up doing this

int textToSpeech(int nextparagraph, const char *storyVariable[], int arraysize) {
 	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
 	if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && storyVariable[nextparagraph][0] != 0) {
 		Common::String paragraph;
 		while (nextparagraph < arraysize && storyVariable[nextparagraph][0] != ' ') {
 			if (!paragraph.empty())
 				paragraph += " ";
 			paragraph += storyVariable[nextparagraph++];
 		while (nextparagraph < arraysize && storyVariable[nextparagraph][0] == ' ') {
 			nextparagraph += 1;
 		ttsMan->say(paragraph, Common::TextToSpeechManager::QUEUE_NO_REPEAT);
 	return nextparagraph;

And calling it with this

if (i == nextparagraph)
 	nextparagraph = textToSpeech(nextparagraph, story, ARRAYSIZE(story));

Enter game after TTS is finished

Check if ttsMan is speaking (unless it’s nullptr) before return

if (ttsMan == nullptr || ttsMan->isSpeaking() == false)

Stop TTS if intro is interrupted

If we press escape, the intro ends and so should the narrating

else if (_event.customType == kGriffonMenu) {
 	if (ttsMan != nullptr)

And That’s about it!

Full changes can be found here.

Leave a Comment