{"id":738,"date":"2023-07-17T13:17:26","date_gmt":"2023-07-17T13:17:26","guid":{"rendered":"https:\/\/tbekk.com\/devstream\/?p=738"},"modified":"2023-07-17T13:17:26","modified_gmt":"2023-07-17T13:17:26","slug":"texttospeech-improvements-for-qt-6-6","status":"publish","type":"post","link":"https:\/\/tbekk.com\/devstream\/2023\/07\/17\/texttospeech-improvements-for-qt-6-6\/","title":{"rendered":"TextToSpeech improvements for Qt 6.6"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-text-color has-light-gray-color has-alpha-channel-opacity has-light-gray-background-color has-background is-style-wide\"\/>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-normal-font-size\"><em><strong>Link: <\/strong><a href=\"https:\/\/www.qt.io\/blog\/qt-texttospeech-improvements-in-qt-6.6\">qt.io\/blog<\/a><\/em><\/li>\n\n\n\n<li class=\"has-normal-font-size\"><em><strong>Author: <\/strong><a href=\"https:\/\/www.qt.io\/blog\/author\/volker-hilsheimer\">Volker Hilsheimer<\/a><\/em><\/li>\n\n\n\n<li class=\"has-normal-font-size\"><em><strong>Publication date: <\/strong>July 17th, 2023<\/em><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-text-color has-light-gray-color has-alpha-channel-opacity has-light-gray-background-color has-background is-style-wide\"\/>\n\n\n\n<p>When we&nbsp;<a href=\"https:\/\/www.qt.io\/blog\/qt-speech-coming-to-qt-6.4?hsLang=en\" rel=\"noreferrer noopener\" target=\"_blank\">announced the Qt 6 port of Qt Speech for Qt 6.4<\/a>, one of the comments pointed out that the module would be more valuable if applications could access the generated speech audio data. Qt 6.6 introduces exactly that, plus a few more features and API improvements.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Synthesizing audio data from text<\/h3>\n\n\n\n<p>The QTextToSpeech C++ class has learned how to generate the speech audio as PCM data. In addition to using&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#say\" rel=\"noreferrer noopener\" target=\"_blank\">QTextToSpeech::say(QString)<\/a>, which simply plays the generated audio, applications can call one of the new&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#synthesize-1\" rel=\"noreferrer noopener\" target=\"_blank\">QTextToSpeech::synthesize()<\/a>&nbsp;overloads. These overloads take the input text as well as a slot, i.e. a functor, lambda, free function, or member function pointer (with context object if needed). That slot will then get called whenever a chunk of PCM data is available from the backend, with a&nbsp;<a href=\"https:\/\/doc.qt.io\/qt-6\/qaudiobuffer.html\" rel=\"noreferrer noopener\" target=\"_blank\">QAudioBuffer<\/a>&nbsp;from Qt Multimedia (or, slightly more efficiently, a&nbsp;<a href=\"https:\/\/doc.qt.io\/qt-6\/qaudioformat.html\" rel=\"noreferrer noopener\" target=\"_blank\">QAudioFormat<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/doc.qt.io\/qt-6\/qbytearray.html\" rel=\"noreferrer noopener\" target=\"_blank\">QByteArray<\/a>) describing the format and containing the actual data. Applications can then post-process the PCM data, write it to a file, or cache it for repeated play-backs using Qt Multimedia.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Better process control<\/h3>\n\n\n\n<p>With Qt 6.6, applications will have better control over the flow of the speech generation. The new&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#enqueue\" rel=\"noreferrer noopener\" target=\"_blank\">QTextToSpeech::enqueue<\/a>&nbsp;function adds an utterance to an ongoing text-to-speech process, and the new&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#aboutToSynthesize\" rel=\"noreferrer noopener\" target=\"_blank\">aboutToSynthesize<\/a>&nbsp;signal is emitted before each of the enqueued utterances gets passed to the backend. This allows applications to make modifications to speech attributes, such as voice or pitch, for each utterance in the queue. And while speech audio is being played, QTextToSpeech can now emit the&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#sayingWord\" rel=\"noreferrer noopener\" target=\"_blank\">sayingWord<\/a>&nbsp;signal for each word as it gets spoken, allowing applications to follow the progress and perhaps give visual cues to the user.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Selecting voices made easy<\/h3>\n\n\n\n<p>We made it easier for applications to select a voice for the text-to-speech synthesis. This has been difficult until now, as applications had to first set the correct&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qml-qttexttospeech-texttospeech.html#locale-prop\" rel=\"noreferrer noopener\" target=\"_blank\">locale<\/a>&nbsp;on the QTextToSpeech object, and then pick one of the voices from the list of&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qml-qttexttospeech-texttospeech.html#availableVoices-method\" rel=\"noreferrer noopener\" target=\"_blank\">availableVoices<\/a>. With Qt 6.6, it becomes easy to find a suitable voice matching a combination of criteria:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const auto frenchWomen = textToSpeech-&gt;findVoices(QLocale::French,<br>                                                  QVoice::Female,<br>                                                  QVoice::Adult);\nconst auto norwegians = textToSpeech-&gt;findVoices(QLocale::Norway);\n<\/pre>\n\n\n\n<p>Note how the criteria can include an attribute of a locale (e.g. just &#8220;French&#8221; as a language, or &#8220;Norway&#8221; as the country; a QLocale object always has both defined). This way, your application doesn&#8217;t have to worry about the optimal territory or dialect. To be fair, one shouldn&#8217;t ask a Nynorsk voice to pronounce a Bokm\u00e5l text; but if your system only happens to support one of the Norwegian official languages, then using that will still be an improvement over the English voice of your e.g. navigation system trying to pronounce my old street address in &#8220;Banksjef Fr\u00f8lichs Gate&#8221;.<\/p>\n\n\n\n<p>With the exception of&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#synthesize-1\" rel=\"noreferrer noopener\" target=\"_blank\">QTextToSpeech::synthesize<\/a>&nbsp;(where the code that processes the raw PCM bytes should be written in C++ anyway), all new capabilities are available from QML as well. E.g. the selection of a voice is achieved through an attached&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qml-qttexttospeech-voiceselector.html\" rel=\"noreferrer noopener\" target=\"_blank\">VoiceSelector<\/a>&nbsp;property:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">TextToSpeech {\n    id: femaleEnglishVoice\n    VoiceSelector.gender: Voice.Female\n    VoiceSelector.language: Qt.locale(\"en\")\n}\n<\/pre>\n\n\n\n<p>This will implicitly select the first matching voice, or otherwise leave the voice unchanged.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s left?<\/h3>\n\n\n\n<p>The last significant feature on my Qt TextToSpeech backlog is support for&nbsp;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Speech_Synthesis_Markup_Language\" rel=\"noreferrer noopener\" target=\"_blank\">Speech Synthesis Markup Language<\/a>, or short SSML. A work-in-progress implementation is available on&nbsp;<a href=\"https:\/\/codereview.qt-project.org\/c\/qt\/qtspeech\/+\/405554\" rel=\"noreferrer noopener\" target=\"_blank\">gerrit code review<\/a>, and what I learned from that experiment is that each backend supports a different subset of SSML. Also, the data we get from backends for the new&nbsp;sayingWord&nbsp;signal are indices into the actual text being spoken, not into the XML string. This might be ok, but the feature needs some more thinking; We don&#8217;t want an XML string that works well on one platform to break the output completely on a different platform (but should we then remove XML elements that we know to be currently unsupported?).<\/p>\n\n\n\n<p>Not all new features are available with all backends. In particular, synthesising to PCM data as well as word progress emission require support for the backend. The new&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qtexttospeech.html#engineCapabilities-prop\" rel=\"noreferrer noopener\" target=\"_blank\">QTextToSpeech:engineCapabilities<\/a>&nbsp;API reports which features are implemented by the backend, and we have updated the&nbsp;<a href=\"https:\/\/doc-snapshots.qt.io\/qt6-6.6\/qttexttospeech-engines.html\" rel=\"noreferrer noopener\" target=\"_blank\">backend documentation<\/a>&nbsp;with the relevant details. Applications can now check at runtime which features they can use, but it would of course be best if everything just worked everywhere. Most importantly, it would be great if we could synthesise speech PCM data also with the speech-dispatcher engine. Contributions welcome, although last time I checked, this required some work on speech-dispatcher itself (at least on the documentation).<\/p>\n\n\n\n<p>As for speech support as a whole: the qtspeech repository now covers the direction from text to speech; some research has been done and proof-of-concept implementations for speech recognition are available on&nbsp;<a href=\"https:\/\/codereview.qt-project.org\/c\/qt\/qtspeech\/+\/447837\" rel=\"noreferrer noopener\" target=\"_blank\">gerrit code review<\/a>. We&#8217;d be very interested to learn more about your use-cases for such a module.<\/p>\n\n\n\n<p>And apropos contributions &#8211; around the Qt 6.6 feature freeze we had a public API review of Qt TextToSpeech, and I&#8217;d like to thank Marc, Fabian, and Philippe for taking the time to go through the changes, provide their feedback, and generally help with improving this module!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When we&nbsp;announced the Qt 6 port of Qt Speech for Qt 6.4, one of the comments pointed out that the module would be more valuable if applications could access the&#8230; <a class=\"read-more-link\" href=\"https:\/\/tbekk.com\/devstream\/2023\/07\/17\/texttospeech-improvements-for-qt-6-6\/\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[51,11],"tags":[93,196,195],"class_list":["post-738","post","type-post","status-publish","format-standard","hentry","category-article","category-qt","tag-qt6","tag-text-to-speech","tag-tts"],"_links":{"self":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/738","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/comments?post=738"}],"version-history":[{"count":1,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/738\/revisions"}],"predecessor-version":[{"id":739,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/738\/revisions\/739"}],"wp:attachment":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/media?parent=738"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/categories?post=738"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/tags?post=738"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}