{"id":146,"date":"2021-07-26T12:07:38","date_gmt":"2021-07-26T12:07:38","guid":{"rendered":"https:\/\/blogs.scummvm.org\/twan\/?p=146"},"modified":"2021-07-26T12:07:39","modified_gmt":"2021-07-26T12:07:39","slug":"how-to-add-tts-to-the-intro-of-griffon","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/twan\/2021\/07\/26\/how-to-add-tts-to-the-intro-of-griffon\/","title":{"rendered":"How to add TTS to the intro of Griffon"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Note<\/h2>\n\n\n\n<p>It&#8217;s just an overview.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Find the Text<\/h2>\n\n\n\n<p>In Griffon&#8217;s case, the text is in the code<\/p>\n\n\n\n<p>So just search for &#8220;Ever since I was a child&#8221;, and there it is, in <code>cutscenes.cpp<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const char *story&#091;48] = {\n\t\"The Griffon Legend\",\n\t\"http:\/\/syn9.thehideoutgames.com\/\",\n\t\"\",\n\t\"Programming\/Graphics: Daniel Kennedy\",\n\t\"Music\/Sound effects: David Turner\",\n\t\"\",\n\t\"Porting to GCW-Zero: Dmitry Smagin\",\n\t\"\",\n\t\"\",\n\t\"Story\",\n\t\"Ever since I was a child\",\n\t\"I remember being told the\",\n\t\"Legend of the Griffon Knights,\",<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Pass to Text To Speech Manager<\/h2>\n\n\n\n<p>Then, we would like to pass the text to text to speech manager<\/p>\n\n\n\n<p>To call for TTS manager, first, include this in the header<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"common\/text-to-speech.h\"<\/code><\/pre>\n\n\n\n<p>Then initialize TTS manager like this<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Common::TextToSpeechManager *ttsMan = g_system-&gt;getTextToSpeechManager();<\/code><\/pre>\n\n\n\n<p>Before passing the text to it, we check to see if it is null pointer, to avoid a crash<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (ttsMan != nullptr) \n\tttsMan-&gt;say(\"The Griffon Legend\");<\/code><\/pre>\n\n\n\n<p>We also would like to check if TTS has been enabled already<\/p>\n\n\n\n<p>(This works given that there are proper adjustments to detection.cpp about &#8220;tts_enabed&#8221;)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"common\/config-manager.h\"\n\nif (ttsMan != nullptr &amp;&amp; ConfMan.getBool(\"tts_enabled\"))\t\n\tttsMan-&gt;say(\"The Griffon Legend\");<\/code><\/pre>\n\n\n\n<p>Obviously we would like the whole story to have TTS, so we loop through<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>for (int i = 0; i &lt; ARRAYSIZE(story); i++) {\n \t\/\/ Do the TTS thing\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Staying in pace<\/h2>\n\n\n\n<p>We would like to have the text shown on the screen be in pace with the TTS speaker.<\/p>\n\n\n\n<p>Therefore, we would like to use the same for loop that is used for showing the text on the screen<\/p>\n\n\n\n<p>See this in the same file, under <code>void GriffonEngine::intro()<\/code>, there is a  <code>do<\/code> { <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>for (int i = 0; i &lt; ARRAYSIZE(story); i++) {           \/\/ go through the story array\n\n \tint yy = y + i * 10;                               \/\/ calculate position\n\n\tif (yy &gt; -8 &amp;&amp; yy &lt; 240) {\n\t\tint x = 160 - strlen(story&#091;i]) * 4;            \/\/ when time comes\n\t\tdrawString(_videoBuffer, story&#091;i], x, yy, 4);  \/\/ draw text to the screen\n\t}\n\n  \tif (yy &lt; 10 &amp;&amp; i == ARRAYSIZE(story) - 1) {        \/\/ last paragraph has left the screen\n\t\treturn;                                        \/\/ proceed to game\n\t}<\/code><\/pre>\n\n\n\n<p>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&#8217;t yet<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\nint nextparagraph = 0;               \/\/this is new\n\nfor (int i = 0; i &lt; ARRAYSIZE(story); i++) {\n\n    int yy = y + i * 10;                           \n\n    \/\/ if (i == nextparagraph)     \n        \/\/ do Text To Speech\n        \/\/ update nextparagraph to reflect progress\n\n    if (yy &gt; -8 &amp;&amp; yy &lt; 240) {\n\tint x = 160 - strlen(story&#091;i]) * 4;\n \tdrawString(_videoBuffer, story&#091;i], x, yy, 4);  \n    }\n\n    if (yy &lt; 10 &amp;&amp; i == ARRAYSIZE(story) - 1) {        \n\treturn;\n    }<\/code><\/pre>\n\n\n\n<p>There are also two more issues to be addressed:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>It&#8217;s better to glue sentences that belong to the same paragraph together. The narrating sounds more natural <\/li><li>Queue new paragraphs behind the current paragraph that is being spoken<\/li><\/ol>\n\n\n\n<p>We ended up doing this<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>int textToSpeech(int nextparagraph, const char *storyVariable&#091;], int arraysize) {\n \tCommon::TextToSpeechManager *ttsMan = g_system-&gt;getTextToSpeechManager();\n \tif (ttsMan != nullptr &amp;&amp; ConfMan.getBool(\"tts_enabled\") &amp;&amp; storyVariable&#091;nextparagraph]&#091;0] != 0) {\n \t\tCommon::String paragraph;\n \t\twhile (nextparagraph &lt; arraysize &amp;&amp; storyVariable&#091;nextparagraph]&#091;0] != ' ') {\n \t\t\tif (!paragraph.empty())\n \t\t\t\tparagraph += \" \";\n \t\t\tparagraph += storyVariable&#091;nextparagraph++];\n \t\t}\n \t\twhile (nextparagraph &lt; arraysize &amp;&amp; storyVariable&#091;nextparagraph]&#091;0] == ' ') {\n \t\t\tnextparagraph += 1;\n \t\t}\n \t\tttsMan-&gt;say(paragraph, Common::TextToSpeechManager::QUEUE_NO_REPEAT);\n \t}\n \treturn nextparagraph;\n }\n\n<\/code><\/pre>\n\n\n\n<p>And calling it with this<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (i == nextparagraph)\n \tnextparagraph = textToSpeech(nextparagraph, story, ARRAYSIZE(story));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Enter game after TTS is finished<\/h2>\n\n\n\n<p>Check if ttsMan is speaking (unless it&#8217;s nullptr) before return<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (ttsMan == nullptr || ttsMan-&gt;isSpeaking() == false)\n \treturn;\n\n\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Stop TTS if intro is interrupted<\/h2>\n\n\n\n<p>If we press escape, the intro ends and so should the narrating<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>else if (_event.customType == kGriffonMenu) {\n \tif (ttsMan != nullptr)\n \t\tttsMan-&gt;stop();\n\treturn;<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">And That&#8217;s about it!<\/h2>\n\n\n\n<p>Full changes can be found <a href=\"https:\/\/github.com\/scummvm\/scummvm\/pull\/2912\/files#diff-549c9af52357050e7d8cf4ab2bf51281d33475b4ca2ebcd835aa717bf6d05cb2\" data-type=\"URL\" data-id=\"https:\/\/github.com\/scummvm\/scummvm\/pull\/2912\/files#diff-549c9af52357050e7d8cf4ab2bf51281d33475b4ca2ebcd835aa717bf6d05cb2\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note It&#8217;s just an overview. Find the Text In Griffon&#8217;s case, the text is in the code So just search for &#8220;Ever since I was a child&#8221;, and there it is, in cutscenes.cpp Pass to Text To Speech Manager Then, we would like to pass the text to text to speech manager To call for &#8230; <a title=\"How to add TTS to the intro of Griffon\" class=\"read-more\" href=\"https:\/\/blogs.scummvm.org\/twan\/2021\/07\/26\/how-to-add-tts-to-the-intro-of-griffon\/\">Read more<span class=\"screen-reader-text\">How to add TTS to the intro of Griffon<\/span><\/a><\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[5],"class_list":["post-146","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-griffon"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/posts\/146","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/comments?post=146"}],"version-history":[{"count":8,"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/posts\/146\/revisions"}],"predecessor-version":[{"id":154,"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/posts\/146\/revisions\/154"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/media?parent=146"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/categories?post=146"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/twan\/wp-json\/wp\/v2\/tags?post=146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}