1// Copyright (C) 2015 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
5#include "qtexttospeech.h"
6#include "qtexttospeech_p.h"
7
8#include <QtCore/qcborarray.h>
9#include <QtCore/qdebug.h>
10#include <QtCore/private/qfactoryloader_p.h>
11
12#include <QtMultimedia/qaudiobuffer.h>
13
14QT_BEGIN_NAMESPACE
15
16using namespace Qt::StringLiterals;
17
18Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
19 ("org.qt-project.qt.speech.tts.plugin/6.0",
20 QLatin1String("/texttospeech")))
21
22QMutex QTextToSpeechPrivate::m_mutex;
23
24QTextToSpeechPrivate::QTextToSpeechPrivate(QTextToSpeech *speech)
25 : q_ptr(speech)
26{
27 qRegisterMetaType<QTextToSpeech::State>();
28 qRegisterMetaType<QTextToSpeech::ErrorReason>();
29}
30
31QTextToSpeechPrivate::~QTextToSpeechPrivate()
32{
33}
34
35void QTextToSpeechPrivate::setEngineProvider(const QString &engine, const QVariantMap &params)
36{
37 Q_Q(QTextToSpeech);
38
39 q->stop(boundaryHint: QTextToSpeech::BoundaryHint::Immediate);
40 m_engine.reset();
41
42 m_providerName = engine;
43 if (m_providerName.isEmpty()) {
44 const auto plugins = QTextToSpeechPrivate::plugins();
45 int priority = -1;
46 for (const auto &&[provider, metadata] : plugins.asKeyValueRange()) {
47 const int pluginPriority = metadata.value(QStringLiteral("Priority")).toInteger();
48 if (pluginPriority > priority) {
49 priority = pluginPriority;
50 m_providerName = provider;
51 }
52 }
53 if (m_providerName.isEmpty()) {
54 qCritical() << "No text-to-speech plug-ins were found.";
55 return;
56 }
57 }
58 if (!loadMeta()) {
59 qCritical() << "Text-to-speech plug-in" << m_providerName << "is not supported.";
60 return;
61 }
62 loadPlugin();
63 if (m_plugin) {
64 QString errorString;
65 m_engine.reset(p: m_plugin->createTextToSpeechEngine(parameters: params, parent: nullptr, errorString: &errorString));
66 if (!m_engine) {
67 qCritical() << "Error creating text-to-speech engine" << m_providerName
68 << (errorString.isEmpty() ? QStringLiteral("") : (QStringLiteral(": ") + errorString));
69 }
70 } else {
71 qCritical() << "Error loading text-to-speech plug-in" << m_providerName;
72 }
73
74 if (m_engine) {
75 // We have to maintain the public state separately from the engine's actual
76 // state, as we use it to manage queued texts
77 updateState(newState: m_engine->state());
78 QObjectPrivate::connect(sender: m_engine.get(), signal: &QTextToSpeechEngine::stateChanged,
79 receiverPrivate: this, slot: &QTextToSpeechPrivate::updateState);
80 // The other engine signals are directly forwarded to public API signals
81 QObject::connect(sender: m_engine.get(), signal: &QTextToSpeechEngine::errorOccurred,
82 context: q, slot: &QTextToSpeech::errorOccurred);
83 QObject::connect(sender: m_engine.get(), signal: &QTextToSpeechEngine::sayingWord,
84 context: q, slot: [this, q](const QString &word, qsizetype start, qsizetype length){
85 emit q->sayingWord(word, id: m_currentUtterance, start, length);
86 });
87 } else {
88 m_providerName.clear();
89 }
90}
91
92bool QTextToSpeechPrivate::loadMeta()
93{
94 m_plugin = nullptr;
95 m_metaData = QCborMap();
96
97 QList<QCborMap> candidates = QTextToSpeechPrivate::plugins().values(key: m_providerName);
98
99 int versionFound = -1;
100
101 // figure out which version of the plugin we want
102 for (int i = 0; i < candidates.size(); ++i) {
103 QCborMap meta = candidates[i];
104 if (int ver = meta.value(key: QLatin1String("Version")).toInteger(); ver > versionFound) {
105 versionFound = ver;
106 m_metaData = std::move(meta);
107 }
108 }
109
110 if (m_metaData.isEmpty()) {
111 m_metaData.insert(key: QLatin1String("index"), value_: -1); // not found
112 return false;
113 }
114
115 return true;
116}
117
118void QTextToSpeechPrivate::loadPlugin()
119{
120 int idx = m_metaData.value(key: QLatin1String("index")).toInteger();
121 if (idx < 0) {
122 m_plugin = nullptr;
123 return;
124 }
125 m_plugin = qobject_cast<QTextToSpeechPlugin *>(object: loader()->instance(index: idx));
126}
127
128QMultiHash<QString, QCborMap> QTextToSpeechPrivate::plugins(bool reload)
129{
130 static QMultiHash<QString, QCborMap> plugins;
131 static bool alreadyDiscovered = false;
132 QMutexLocker lock(&m_mutex);
133
134 if (reload == true)
135 alreadyDiscovered = false;
136
137 if (!alreadyDiscovered) {
138 loadPluginMetadata(list&: plugins);
139 alreadyDiscovered = true;
140 }
141 return plugins;
142}
143
144void QTextToSpeechPrivate::loadPluginMetadata(QMultiHash<QString, QCborMap> &list)
145{
146 QFactoryLoader *l = loader();
147 QList<QPluginParsedMetaData> meta = l->metaData();
148 for (int i = 0; i < meta.size(); ++i) {
149 QCborMap obj = meta.at(i).value(k: QtPluginMetaDataKeys::MetaData).toMap();
150 obj.insert(key: QLatin1String("index"), value_: i);
151 list.insert(key: obj.value(key: QLatin1String("Provider")).toString(), value: obj);
152 }
153}
154
155void QTextToSpeechPrivate::updateState(QTextToSpeech::State newState)
156{
157 Q_Q(QTextToSpeech);
158 if (m_state == newState)
159 return;
160
161 if (newState == QTextToSpeech::Ready) {
162 // If we have more text to process, start the next request immediately,
163 // and ignore the transition to Ready (don't emit the signals).
164 if (!m_pendingUtterances.isEmpty()) {
165 const QString nextText = m_pendingUtterances.first();
166 // QTextToSpeech::pause prepends an empty entry to request a pause
167 if (nextText.isEmpty()) {
168 m_state = QTextToSpeech::Paused;
169 m_pendingUtterances.dequeue();
170 } else {
171 const auto nextFunction = [this]{
172 switch (m_state) {
173 case QTextToSpeech::Synthesizing:
174 return &QTextToSpeechEngine::synthesize;
175 case QTextToSpeech::Speaking:
176 case QTextToSpeech::Paused:
177 return &QTextToSpeechEngine::say;
178 default:
179 break;
180 }
181 return decltype(&QTextToSpeechEngine::synthesize)(nullptr);
182 }();
183 if (nextFunction) {
184 const auto oldState = m_state;
185 emit q->aboutToSynthesize(id: m_currentUtterance);
186 // connected slot could have called pause or stop, in which
187 // case the state changed or the pendingTexts got reset.
188 if (m_state == oldState && !m_pendingUtterances.isEmpty()) {
189 m_pendingUtterances.dequeue();
190 ++m_currentUtterance;
191 (m_engine.get()->*nextFunction)(nextText);
192 return;
193 } else if (m_state == QTextToSpeech::Paused) {
194 // In case of pause(), empty strings got inserted.
195 // We are already idle, so remove them again.
196 while (!m_pendingUtterances.isEmpty() && m_pendingUtterances.first().isEmpty())
197 m_pendingUtterances.dequeue();
198 return;
199 }
200 // in case of stop(), disconnect and update the state
201 disconnectSynthesizeFunctor();
202 }
203 }
204 } else {
205 // If we are done synthesizing and the functor-overload was used,
206 // clear the temporary connection.
207 disconnectSynthesizeFunctor();
208 }
209 }
210 m_state = newState;
211 emit q->stateChanged(state: newState);
212}
213
214void QTextToSpeechPrivate::disconnectSynthesizeFunctor()
215{
216 if (m_slotObject) {
217 m_slotObject->destroyIfLastRef();
218 m_slotObject = nullptr;
219 m_engine->disconnect(m_synthesizeConnection);
220 }
221}
222
223/*!
224 \class QTextToSpeech
225 \brief The QTextToSpeech class provides a convenient access to text-to-speech engines.
226 \inmodule QtTextToSpeech
227
228 Use \l say() to start reading text to the default audio device, and
229 \l stop(), \l pause(), and \l resume() to control the reading of the text.
230
231 \snippet hello_speak/mainwindow.cpp say
232 \snippet hello_speak/mainwindow.cpp stop
233 \snippet hello_speak/mainwindow.cpp pause
234 \snippet hello_speak/mainwindow.cpp resume
235
236 To synthesize text into PCM data for further processing, use synthesize().
237
238 Use \l findVoices() to get a list of matching voices, or use \l
239 availableVoices() to get the list of voices that support the current
240 locale. Change the \l locale property, using one of the \l availableLocales()
241 that is a good match for the language that the input text is in, and for
242 the accent of the desired voice output. This will change the list of
243 available voices on most platforms. Then use one of the available voices in
244 a call to \l setVoice().
245
246 Not every engine supports all features. Use the engineCapabilities() function to
247 test which features are available, and adjust the usage of the class accordingly.
248
249 \note Which locales and voices the engine supports depends usually on the Operating
250 System configuration. E.g. on macOS, end users can install voices through the
251 \e Accessibility panel in \e{System Preferences}.
252*/
253
254/*!
255 \qmltype TextToSpeech
256 \inqmlmodule QtTextToSpeech
257 \brief The TextToSpeech type provides access to text-to-speech engines.
258
259 Use \l say() to start reading text to the default audio device, and
260 \l stop(), \l pause(), and \l resume() to control the reading of the text.
261
262 \snippet quickspeech/Main.qml initialize
263 \codeline
264 \dots
265 \codeline
266 \snippet quickspeech/Main.qml say0
267 \snippet quickspeech/Main.qml say1
268 \snippet quickspeech/Main.qml pause
269 \snippet quickspeech/Main.qml resume
270 \dots
271
272 To synthesize text into PCM data for further processing, use synthesize().
273
274 To set a voice, use the VoiceSelector attached property like this:
275
276 \code
277 TextToSpeech {
278 VoiceSelector.locale: Qt.locale("en_UK")
279 VoiceSelector.gender: Voice.Male
280 }
281 \endcode
282
283 The first voice that matches all specified criteria will be used. If no voice
284 matches all criteria, then the voice will not change.
285
286 Alternatively, use \l findVoices() to get a list of matching voices, or use
287 \l availableVoices() to get the list of voices that support the current
288 locale. Change the \l locale property, using one of the \l availableLocales()
289 that is a good match for the language that the input text is in, and for
290 the accent of the desired voice output. This will change the list of
291 available voices on most platforms. Then use one of the available voices in
292 the \l voice property.
293
294 Not every engine supports all features. Use the engineCapabilities()
295 function to test which features are available, and adjust the usage of the
296 type accordingly.
297
298 \note Which locales and voices the engine supports depends usually on the Operating
299 System configuration. E.g. on macOS, end users can install voices through the
300 \e Accessibility panel in \e{System Preferences}.
301*/
302
303/*!
304 \enum QTextToSpeech::State
305
306 \brief This enum describes the current state of the text-to-speech engine.
307
308 \value Ready The synthesizer is ready to start a new text. This is
309 also the state after a text was finished.
310 \value Speaking Text is being spoken.
311 \value Synthesizing Text is being synthesized into PCM data. The synthesized()
312 signal will be emitted with chunks of data.
313 \value Paused The synthesis was paused and can be resumed with \l resume().
314 \value Error An error has occurred. Details are given by \l errorReason().
315
316 \sa QTextToSpeech::ErrorReason errorReason() errorString()
317*/
318
319/*!
320 \enum QTextToSpeech::ErrorReason
321
322 \brief This enum describes the current error, if any, of the QTextToSpeech engine.
323
324 \value NoError No error has occurred.
325 \value Initialization The backend could not be initialized, e.g. due to
326 a missing driver or operating system requirement.
327 \value Configuration The given backend configuration is inconsistent, e.g.
328 due to wrong voice name or parameters.
329 \value Input The given text could not be synthesized, e.g. due to
330 invalid size or characters.
331 \value Playback Audio playback failed e.g. due to missing audio device,
332 wrong format or audio streaming interruption.
333
334 Use \l errorReason() to obtain the current error and \l errorString() to get the
335 related error message.
336
337 \sa errorOccurred()
338*/
339
340/*!
341 \enum QTextToSpeech::BoundaryHint
342
343 \brief describes when speech should be stopped and paused.
344
345 \value Default Uses the engine specific default behavior.
346 \value Immediate The engine should stop playback immediately.
347 \value Word Stop speech when the current word is finished.
348 \value Sentence Stop speech when the current sentence is finished.
349 \value [since 6.6] Utterance
350 Stop speech when the current utterance is finished.
351 An utterance is the block of text used in a call to
352 say() or enqueue().
353
354 \note These are hints to the engine. The current engine might not support
355 all options.
356*/
357
358/*!
359 Loads a text-to-speech engine from a plug-in that uses the default
360 engine plug-in and constructs a QTextToSpeech object as the child
361 of \a parent.
362
363 The default engine is platform-specific.
364
365 If the engine initializes correctly, then the \l state of the engine will
366 change to QTextToSpeech::Ready; note that this might happen asynchronously.
367 If the plugin fails to load, then \l state will be set to QTextToSpeech::Error.
368
369 \sa availableEngines()
370*/
371QTextToSpeech::QTextToSpeech(QObject *parent)
372 : QTextToSpeech(QString(), QVariantMap(), parent)
373{
374}
375
376/*!
377 Loads a text-to-speech engine from a plug-in that matches parameter \a engine and
378 constructs a QTextToSpeech object as the child of \a parent.
379
380 If \a engine is empty, the default engine plug-in is used. The default
381 engine is platform-specific.
382
383 If the engine initializes correctly, the \l state of the engine will be set
384 to QTextToSpeech::Ready. If the plugin fails to load, or if the engine fails to
385 initialize, the engine's \l state will be set to QTextToSpeech::Error.
386
387 \sa availableEngines()
388*/
389QTextToSpeech::QTextToSpeech(const QString &engine, QObject *parent)
390 : QTextToSpeech(engine, QVariantMap(), parent)
391{
392}
393
394/*!
395 \since 6.4
396
397 Loads a text-to-speech engine from a plug-in that matches parameter \a engine and
398 constructs a QTextToSpeech object as the child of \a parent, passing \a params
399 through to the engine.
400
401 If \a engine is empty, the default engine plug-in is used. The default
402 engine is platform-specific. Which key/value pairs in \a params are supported
403 depends on the engine. See \l{Qt TextToSpeech Engines}{the engine documentation}
404 for details. Unsupported entries will be ignored.
405
406 If the engine initializes correctly, the \l state of the engine will be set
407 to QTextToSpeech::Ready. If the plugin fails to load, or if the engine fails to
408 initialize, the engine's \l state will be set to QTextToSpeech::Error.
409
410 \sa availableEngines()
411*/
412QTextToSpeech::QTextToSpeech(const QString &engine, const QVariantMap &params, QObject *parent)
413 : QObject(*new QTextToSpeechPrivate(this), parent)
414{
415 Q_D(QTextToSpeech);
416 // allow QDeclarativeTextToSpeech to skip initialization until the component
417 // is complete
418 if (engine != u"none"_s)
419 d->setEngineProvider(engine, params);
420 else
421 d->m_providerName = engine;
422}
423
424/*!
425 Destroys this QTextToSpeech object, stopping any speech.
426*/
427QTextToSpeech::~QTextToSpeech()
428{
429 stop(boundaryHint: QTextToSpeech::BoundaryHint::Immediate);
430}
431
432/*!
433 \qmlproperty string TextToSpeech::engine
434
435 The engine used to synthesize text to speech.
436
437 Changing the engine stops any ongoing speech.
438
439 On most platforms, changing the engine will update the list of
440 \l{availableLocales()}{available locales} and \l{availableVoices()}{available voices}.
441*/
442
443/*!
444 \property QTextToSpeech::engine
445 \brief the engine used to synthesize text to speech.
446 \since 6.4
447
448 Changing the engine stops any ongoing speech.
449
450 On most platforms, changing the engine will update the list of
451 \l{availableLocales()}{available locales} and \l{availableVoices()}{available voices}.
452*/
453
454/*!
455 \since 6.4
456 Sets the engine used by this QTextToSpeech object to \a engine, passing
457 \a params through to the engine constructor.
458
459 \return whether \a engine could be set successfully.
460
461 Which key/value pairs in \a params are supported depends on the engine.
462 See \l{Qt TextToSpeech Engines}{the engine documentation} for details.
463 Unsupported entries will be ignored.
464*/
465bool QTextToSpeech::setEngine(const QString &engine, const QVariantMap &params)
466{
467 Q_D(QTextToSpeech);
468 if (d->m_providerName == engine && params.isEmpty())
469 return true;
470
471 // read values from the old engine
472 if (d->m_engine) {
473 d->m_storedPitch = d->m_engine->pitch();
474 d->m_storedRate = d->m_engine->rate();
475 d->m_storedVolume = d->m_engine->volume();
476 }
477
478 d->setEngineProvider(engine, params);
479
480 emit engineChanged(engine: d->m_providerName);
481 d->updateState(newState: d->m_engine ? d->m_engine->state()
482 : QTextToSpeech::Error);
483
484 // Restore values from the previous engine, or from
485 // property setters before the engine was initialized.
486 if (d->m_engine) {
487 if (!qIsNaN(d: d->m_storedPitch))
488 d->m_engine->setPitch(d->m_storedPitch);
489 if (!qIsNaN(d: d->m_storedRate))
490 d->m_engine->setRate(d->m_storedRate);
491 if (!qIsNaN(d: d->m_storedVolume))
492 d->m_engine->setVolume(d->m_storedVolume);
493
494 // setting the engine might have changed these values
495 if (double realPitch = pitch(); d->m_storedPitch != realPitch)
496 emit pitchChanged(pitch: realPitch);
497 if (double realRate = rate(); d->m_storedRate != realRate)
498 emit rateChanged(rate: realRate);
499 if (double realVolume = volume(); d->m_storedVolume != realVolume)
500 emit volumeChanged(volume: realVolume);
501
502 emit localeChanged(locale: locale());
503 emit voiceChanged(voice: voice());
504 }
505 return d->m_engine.get();
506}
507
508QString QTextToSpeech::engine() const
509{
510 Q_D(const QTextToSpeech);
511 return d->m_providerName;
512}
513
514/*!
515 \enum QTextToSpeech::Capability
516 \since 6.6
517 \brief This enum describes the capabilities of a text-to-speech engine.
518
519 \value None The engine implements none of the capabilities.
520 \value Speak The engine can play audio output from text.
521 \value PauseResume The engine can pause and then resume the audo output.
522 \value WordByWordProgress The engine emits the sayingWord() signal for
523 each word that gets spoken.
524 \value Synthesize The engine can \l{synthesize()}{synthesize} PCM
525 audio data from text.
526
527 \sa engineCapabilities()
528*/
529
530/*!
531 \qmlproperty enumeration TextToSpeech::engineCapabilities
532 \brief This property holds the capabilities implemented by the current engine.
533 \since 6.6
534
535 \sa engine, QTextToSpeech::Capability
536*/
537
538/*!
539 \property QTextToSpeech::engineCapabilities
540 \brief the capabilities implemented by the current engine
541 \since 6.6
542
543 \sa engine
544*/
545QTextToSpeech::Capabilities QTextToSpeech::engineCapabilities() const
546{
547 Q_D(const QTextToSpeech);
548
549 QTextToSpeech::Capabilities caps = d->m_engine
550 ? d->m_engine->capabilities()
551 : QTextToSpeech::Capability::None;
552 if (caps != QTextToSpeech::Capability::None)
553 return caps;
554 if (d->m_providerName.isEmpty()) {
555 qCritical() << "No engine set.";
556 return caps;
557 }
558
559 const QMetaEnum capEnum = QMetaEnum::fromType<QTextToSpeech::Capabilities>();
560 const auto plugin = QTextToSpeechPrivate::plugins().value(key: d->m_providerName);
561 const auto capNames = plugin.value(QStringLiteral("Capabilities")).toArray();
562
563 // compatibility: plugins that don't set Features can only speak
564 if (capNames.isEmpty())
565 caps |= QTextToSpeech::Capability::Speak;
566
567 for (const auto capName : capNames) {
568 const auto capString = capName.toString().toUtf8();
569 bool ok = false;
570 const QTextToSpeech::Capability capFlag = QTextToSpeech::Capability(capEnum.keyToValue(key: capString, ok: &ok));
571 if (!ok) {
572 qWarning(msg: "Unknown capability: '%s' doesn't map to any QTextToSpeech::Capability value",
573 capString.constData());
574 } else {
575 caps |= capFlag;
576 }
577 }
578 return caps;
579}
580
581/*!
582 \qmlmethod list<string> TextToSpeech::availableEngines()
583
584 Holds the list of supported text-to-speech engine plug-ins.
585*/
586
587/*!
588 Gets the list of supported text-to-speech engine plug-ins.
589
590 \sa engine
591*/
592QStringList QTextToSpeech::availableEngines()
593{
594 return QTextToSpeechPrivate::plugins().keys();
595}
596
597/*!
598 \qmlproperty enumeration TextToSpeech::state
599 \brief This property holds the current state of the speech synthesizer.
600
601 \sa QTextToSpeech::State say() stop() pause()
602
603 \snippet quickspeech/Main.qml stateChanged
604*/
605
606/*!
607 \property QTextToSpeech::state
608 \brief the current state of the speech synthesizer.
609
610 \snippet hello_speak/mainwindow.cpp stateChanged
611
612 Use \l say() to start synthesizing text with the current \l voice and \l locale.
613*/
614QTextToSpeech::State QTextToSpeech::state() const
615{
616 Q_D(const QTextToSpeech);
617 return d->m_state;
618}
619
620/*!
621 \qmlsignal TextToSpeech::aboutToSynthesize(number id)
622
623 \since 6.6
624
625 This signal gets emitted just before the engine starts to synthesize the
626 speech audio for \a id. Applications can use this signal to make last-minute
627 changes to \l voice attributes, or to track the process of text enqueued
628 via enqueue().
629
630 \sa enqueue(), voice
631*/
632
633/*!
634 \fn void QTextToSpeech::aboutToSynthesize(qsizetype id)
635 \since 6.6
636
637 This signal gets emitted just before the engine starts to synthesize the
638 speech audio for \a id. The \a id is the value returned by a call to enqueue(),
639 Applications can use this signal to make last-minute changes to \l voice
640 attributes, or to track the process of text enqueued via enqueue().
641
642 \sa enqueue(), synthesize(), voice
643*/
644
645/*!
646 \qmlsignal TextToSpeech::sayingWord(string word, int id, int start, int length)
647 \since 6.6
648
649 This signal is emitted when the \a word, which is the slice of text indicated
650 by \a start and \a length in the utterance \a id, gets played to the audio device.
651
652 \note This signal requires that the engine has the
653 \l {QTextToSpeech::Capability::}{WordByWordProgress} capability.
654
655 The following code highlights the word that is spoken in a TextArea \c input:
656 \snippet quickspeech/Main.qml sayingWord
657
658 \sa QTextToSpeech::Capability, say()
659*/
660
661/*!
662 \fn void QTextToSpeech::sayingWord(const QString &word, qsizetype id, qsizetype start, qsizetype length)
663 \since 6.6
664
665 This signal is emitted when the \a word, which is the slice of text indicated
666 by \a start and \a length in the utterance \a id, gets played to the audio device.
667
668 \note This signal requires that the engine has the
669 \l {QTextToSpeech::Capability::}{WordByWordProgress} capability.
670
671 \sa Capability, say()
672*/
673
674/*!
675 \qmlsignal void TextToSpeech::errorOccurred(enumeration reason, string errorString)
676
677 This signal is emitted after an error occurred and the \l state has been set to
678 \c TextToSpeech.Error. The \a reason parameter specifies the type of error,
679 and the \a errorString provides a human-readable error description.
680
681 \sa state errorReason(), errorString()
682*/
683
684/*!
685 \fn void QTextToSpeech::errorOccurred(QTextToSpeech::ErrorReason reason, const QString &errorString)
686
687 This signal is emitted after an error occurred and the \l state has been set to
688 QTextToSpeech::Error. The \a reason parameter specifies the type of error,
689 and the \a errorString provides a human-readable error description.
690
691 QTextToSpeech::ErrorReason is not a registered metatype, so for queued
692 connections, you will have to register it with Q_DECLARE_METATYPE() and
693 qRegisterMetaType().
694
695 \sa errorReason(), errorString(), {Creating Custom Qt Types}
696*/
697
698/*!
699 \qmlmethod enumeration TextToSpeech::errorReason()
700 \return the reason why the engine has reported an error.
701
702 \sa QTextToSpeech::ErrorReason
703*/
704
705/*!
706 \return the reason why the engine has reported an error.
707
708 \sa state errorOccurred()
709*/
710QTextToSpeech::ErrorReason QTextToSpeech::errorReason() const
711{
712 Q_D(const QTextToSpeech);
713 if (d->m_engine)
714 return d->m_engine->errorReason();
715 return QTextToSpeech::ErrorReason::Initialization;
716}
717
718/*!
719 \qmlmethod string TextToSpeech::errorString()
720 \return the current engine error message.
721*/
722
723/*!
724 \return the current engine error message.
725
726 \sa errorOccurred()
727*/
728QString QTextToSpeech::errorString() const
729{
730 Q_D(const QTextToSpeech);
731 if (d->m_engine)
732 return d->m_engine->errorString();
733 return tr(s: "Text to speech engine not initialized");
734}
735
736/*!
737 \qmlmethod TextToSpeech::say(string text)
738
739 Starts synthesizing the \a text.
740
741 This function starts sythesizing the speech asynchronously, and reads the text to the
742 default audio output device.
743
744 \snippet quickspeech/Main.qml say0
745 \snippet quickspeech/Main.qml say1
746
747 \note All in-progress readings are stopped before beginning to read the recently
748 synthesized text.
749
750 The current state is available using the \l state property, and is
751 set to \l {QTextToSpeech::Speaking} once the reading starts. When the reading is done,
752 \l state will be set to \l {QTextToSpeech::Ready}.
753
754 \sa stop(), pause(), resume()
755*/
756
757/*!
758 Starts speaking the \a text.
759
760 This function starts sythesizing the speech asynchronously, and reads the text to the
761 default audio output device.
762
763 \snippet hello_speak/mainwindow.cpp say
764
765 \note All in-progress readings are stopped before beginning to read the recently
766 synthesized text.
767
768 The current state is available using the \l state property, and is
769 set to \l Speaking once the reading starts. When the reading is done,
770 \l state will be set to \l Ready.
771
772 \sa enqueue(), stop(), pause(), resume(), synthesize()
773*/
774void QTextToSpeech::say(const QString &text)
775{
776 Q_D(QTextToSpeech);
777 d->m_pendingUtterances = {};
778 d->m_utteranceCounter = 1;
779 if (d->m_engine) {
780 emit aboutToSynthesize(id: 0);
781 d->m_engine->say(text);
782 }
783}
784
785/*!
786 \qmlmethod TextToSpeech::enqueue(string utterance)
787 \since 6.6
788
789 Adds \a utterance to the queue of text to be spoken, and starts speaking.
790
791 If the engine's \l state is currently \c Ready, \a utterance will be spoken
792 immediately. Otherwise, the engine will start to speak \a utterance once it
793 has finished speaking the current text.
794
795 Each time the engine proceeds to the next text entry in the queue, the
796 aboutToSynthesize() signal gets emitted. This allows applications to keep
797 track of the progress, and to make last-minute changes to voice attributes.
798
799 Calling stop() clears the queue.
800
801 \sa say(), stop(), aboutToSynthesize()
802*/
803
804/*!
805 \since 6.6
806
807 Adds \a utterance to the queue of texts to be spoken, and starts speaking.
808 Returns the index of the text in the queue, or -1 in case of an error.
809
810 If the engine's \l state is currently \c Ready, \a utterance will be spoken
811 immediately. Otherwise, the engine will start to speak \a utterance once it
812 has finished speaking the current text.
813
814 Each time the engine proceeds to the next text entry in the queue, the
815 aboutToSynthesize() signal gets emitted. This allows applications to keep
816 track of the progress, and to make last-minute changes to voice attributes.
817
818 Calling stop() clears the queue. To pause the engine at the end of a text,
819 use the \l {QTextToSpeech::BoundaryHint::}{Utterance} boundary hint.
820
821 \sa say(), stop(), aboutToSynthesize(), synthesize()
822*/
823qsizetype QTextToSpeech::enqueue(const QString &utterance)
824{
825 Q_D(QTextToSpeech);
826 if (!d->m_engine || utterance.isEmpty())
827 return -1;
828
829 switch (d->m_engine->state()) {
830 case QTextToSpeech::Error:
831 return -1;
832 case QTextToSpeech::Ready:
833 emit aboutToSynthesize(id: 0);
834 d->m_engine->say(text: utterance);
835 break;
836 case QTextToSpeech::Speaking:
837 case QTextToSpeech::Synthesizing:
838 case QTextToSpeech::Paused:
839 d->m_pendingUtterances.enqueue(t: utterance);
840 break;
841 }
842
843 return d->m_utteranceCounter++;
844}
845
846/*!
847 \fn template<typename Functor> void QTextToSpeech::synthesize(
848 const QString &text, Functor &&functor)
849 \fn template<typename Functor> void QTextToSpeech::synthesize(
850 const QString &text, const QObject *context, Functor &&functor)
851 \since 6.6
852
853 Synthesizes the \a text into raw audio data.
854
855 This function synthesizes the speech asynchronously into raw audio data.
856 When data is available, the \a functor will be called as
857 \c {functor(QAudioFormat format, QByteArray bytes)}, with \c format
858 describing the \l {QAudioFormat}{format} of the data in \c bytes;
859 or as \c {functor(QAudioBuffer &buffer)}.
860
861 The \l state property is set to \l Synthesizing when the synthesis starts,
862 and to \l Ready once the synthesis is finished. While synthesizing, the
863 \a functor might be called multiple times, possibly with changing values
864 for \c format.
865
866 The \a functor can be a callable, like a lambda or free function, with an
867 optional \a context object:
868
869 \code
870 tts.synthesize("Hello world", [](const QAudioFormat &format, const QByteArray &bytes){
871 // process data according to format
872 });
873 \endcode
874
875 or a member function of the \a context object:
876
877 \code
878 struct PCMProcessor : QObject
879 {
880 void processData(const QAudioFormat &format, const QByteArray &bytes)
881 {
882 // process data according to format
883 }
884 } processor;
885 tts.synthesize("Hello world", &processor, &PCMProcessor::processData);
886 \endcode
887
888 If \a context is destroyed, then the \a functor will no longer get called.
889
890 \note This API requires that the engine has the
891 \l {QTextToSpeech::Capability::}{Synthesize} capability.
892
893 \sa say(), stop()
894*/
895
896/*!
897 \internal
898
899 Handles the engine's synthesized() signal to call \a slotObj on the \a context
900 object. The slot object and the temporary connection are stored and released
901 in updateState() when the state of the engine transitions back to Ready.
902*/
903void QTextToSpeech::synthesizeImpl(const QString &text,
904 QtPrivate::QSlotObjectBase *slotObj, const QObject *context,
905 SynthesizeOverload overload)
906{
907 Q_D(QTextToSpeech);
908 Q_ASSERT(slotObj);
909 if (d->m_slotObject)
910 d->m_slotObject->destroyIfLastRef();
911 d->m_slotObject = slotObj;
912 const auto receive = [d, context, overload](const QAudioFormat &format, const QByteArray &bytes){
913 Q_ASSERT(d->m_slotObject);
914 if (overload == SynthesizeOverload::AudioBuffer) {
915 const QAudioBuffer buffer(bytes, format);
916 void *args[] = {nullptr, const_cast<QAudioBuffer *>(&buffer)};
917 d->m_slotObject->call(r: const_cast<QObject *>(context), a: args);
918 } else {
919 void *args[] = {nullptr,
920 const_cast<QAudioFormat *>(&format),
921 const_cast<QByteArray *>(&bytes)};
922 d->m_slotObject->call(r: const_cast<QObject *>(context), a: args);
923 }
924 };
925 d->m_synthesizeConnection = connect(sender: d->m_engine.get(), signal: &QTextToSpeechEngine::synthesized,
926 context: context ? context : this, slot: receive);
927
928 if (!d->m_engine)
929 return;
930
931 if (d->m_engine->state() == QTextToSpeech::Synthesizing)
932 d->m_pendingUtterances.enqueue(t: text);
933 else
934 d->m_engine->synthesize(text);
935}
936
937/*!
938 \qmlmethod TextToSpeech::stop(BoundaryHint boundaryHint)
939
940 Stops the current reading at \a boundaryHint, and clears the
941 queue of pending texts.
942
943 The reading cannot be resumed. Whether the \a boundaryHint is
944 respected depends on the engine.
945
946 \sa say(), enqueue(), pause(), QTextToSpeech::BoundaryHint
947*/
948
949/*!
950 Stops the current reading at \a boundaryHint, and clears the
951 queue of pending texts.
952
953 The reading cannot be resumed. Whether the \a boundaryHint is
954 respected depends on the engine.
955
956 \sa say(), enqueue(), pause()
957*/
958void QTextToSpeech::stop(BoundaryHint boundaryHint)
959{
960 Q_D(QTextToSpeech);
961 d->m_pendingUtterances = {};
962 d->m_utteranceCounter = 0;
963 if (d->m_engine) {
964 if (boundaryHint == QTextToSpeech::BoundaryHint::Immediate)
965 d->disconnectSynthesizeFunctor();
966 d->m_engine->stop(boundaryHint);
967 }
968}
969
970/*!
971 \qmlmethod TextToSpeech::pause(BoundaryHint boundaryHint)
972
973 Pauses the current speech at \a boundaryHint.
974
975 Whether the \a boundaryHint is respected depends on the \l engine.
976
977 \sa resume(), QTextToSpeech::BoundaryHint,
978 {QTextToSpeech::Capabilities}{PauseResume}
979*/
980
981/*!
982 Pauses the current speech at \a boundaryHint.
983
984 Whether the \a boundaryHint is respected depends on the \l engine.
985
986 \sa resume(), {QTextToSpeech::Capabilities}{PauseResume}
987*/
988void QTextToSpeech::pause(BoundaryHint boundaryHint)
989{
990 Q_D(QTextToSpeech);
991 if (!d->m_engine || d->m_state != QTextToSpeech::Speaking)
992 return;
993
994 if (boundaryHint == BoundaryHint::Utterance) {
995 if (d->m_pendingUtterances.isEmpty() || !d->m_pendingUtterances.first().isEmpty())
996 d->m_pendingUtterances.prepend(t: QString());
997 }
998 // pause called in response to aboutToSynthesize
999 if (d->m_engine->state() == QTextToSpeech::Ready) {
1000 d->updateState(newState: QTextToSpeech::Paused);
1001 } else {
1002 d->m_engine->pause(boundaryHint);
1003 }
1004}
1005
1006/*!
1007 \qmlmethod TextToSpeech::resume()
1008
1009 Resume speaking after \l pause() has been called.
1010
1011 \sa pause()
1012*/
1013
1014/*!
1015 Resume speaking after \l pause() has been called.
1016
1017 \note On Android, resuming paused speech will restart from the beginning.
1018 This is a limitation of the underlying text-to-speech engine.
1019
1020 \sa pause()
1021*/
1022void QTextToSpeech::resume()
1023{
1024 Q_D(QTextToSpeech);
1025 if (d->m_state != QTextToSpeech::Paused)
1026 return;
1027
1028 if (d->m_engine) {
1029 // If we are pausing before proceeding with the next utterance,
1030 // then continue with the next pending text.
1031 if (d->m_engine->state() == QTextToSpeech::Ready)
1032 d->updateState(newState: QTextToSpeech::Ready);
1033 else
1034 d->m_engine->resume();
1035 }
1036}
1037
1038/*!
1039 \qmlproperty double TextToSpeech::pitch
1040
1041 This property hold the voice pitch, ranging from -1.0 to 1.0.
1042
1043 The default of 0.0 is the normal speech pitch.
1044*/
1045
1046/*!
1047 \property QTextToSpeech::pitch
1048 \brief the voice pitch, ranging from -1.0 to 1.0.
1049
1050 The default of 0.0 is the normal speech pitch.
1051*/
1052
1053void QTextToSpeech::setPitch(double pitch)
1054{
1055 Q_D(QTextToSpeech);
1056 pitch = qBound(min: -1.0, val: pitch, max: 1.0);
1057
1058 if (!d->m_engine) {
1059 if (d->m_storedPitch != pitch) {
1060 d->m_storedPitch = pitch;
1061 emit pitchChanged(pitch);
1062 }
1063 return;
1064 }
1065 if (d->m_engine->pitch() == pitch)
1066 return;
1067 if (d->m_engine && d->m_engine->setPitch(pitch))
1068 emit pitchChanged(pitch);
1069}
1070
1071double QTextToSpeech::pitch() const
1072{
1073 Q_D(const QTextToSpeech);
1074 if (d->m_engine)
1075 return d->m_engine->pitch();
1076 return qIsNaN(d: d->m_storedPitch) ? 0.0 : d->m_storedPitch;
1077}
1078
1079/*!
1080 \qmlproperty double TextToSpeech::rate
1081
1082 \brief This property holds the current voice rate, ranging from -1.0 to 1.0.
1083
1084 The default of 0.0 is the normal speech flow.
1085*/
1086
1087/*!
1088 \property QTextToSpeech::rate
1089 \brief the current voice rate, ranging from -1.0 to 1.0.
1090
1091 The default value of 0.0 is normal speech flow.
1092*/
1093void QTextToSpeech::setRate(double rate)
1094{
1095 Q_D(QTextToSpeech);
1096 rate = qBound(min: -1.0, val: rate, max: 1.0);
1097
1098 if (!d->m_engine) {
1099 if (d->m_storedRate != rate) {
1100 d->m_storedRate = rate;
1101 emit rateChanged(rate);
1102 }
1103 return;
1104 }
1105 if (d->m_engine->rate() == rate)
1106 return;
1107 if (d->m_engine && d->m_engine->setRate(rate))
1108 emit rateChanged(rate);
1109}
1110
1111double QTextToSpeech::rate() const
1112{
1113 Q_D(const QTextToSpeech);
1114 if (d->m_engine)
1115 return d->m_engine->rate();
1116 return qIsNaN(d: d->m_storedRate) ? 0.0 : d->m_storedRate;
1117}
1118
1119/*!
1120 \qmlproperty double TextToSpeech::volume
1121
1122 \brief This property holds the current volume, ranging from 0.0 to 1.0.
1123
1124 The default value is the platform's default volume.
1125*/
1126
1127/*!
1128 \property QTextToSpeech::volume
1129 \brief the current volume, ranging from 0.0 to 1.0.
1130
1131 The default value is the platform's default volume.
1132*/
1133void QTextToSpeech::setVolume(double volume)
1134{
1135 Q_D(QTextToSpeech);
1136 volume = qBound(min: 0.0, val: volume, max: 1.0);
1137
1138 if (!d->m_engine) {
1139 if (d->m_storedVolume != volume) {
1140 d->m_storedVolume = volume;
1141 emit volumeChanged(volume);
1142 }
1143 return;
1144 }
1145 if (d->m_engine->volume() == volume)
1146 return;
1147 if (d->m_engine->setVolume(volume))
1148 emit volumeChanged(volume);
1149}
1150
1151double QTextToSpeech::volume() const
1152{
1153 Q_D(const QTextToSpeech);
1154 if (d->m_engine)
1155 return d->m_engine->volume();
1156 return qIsNaN(d: d->m_storedVolume) ? 0.0 : d->m_storedVolume;
1157}
1158
1159/*!
1160 \qmlproperty locale TextToSpeech::locale
1161
1162 \brief This property holds the current locale in use.
1163
1164 By default, the system locale is used.
1165
1166 \sa voice
1167*/
1168
1169/*!
1170 \property QTextToSpeech::locale
1171 \brief the current locale in use.
1172
1173 By default, the system locale is used.
1174
1175 On some platforms, changing the locale will update the list of
1176 \l{availableVoices()}{available voices}, and if the current voice is not
1177 available with the new locale, a new voice will be set.
1178
1179 \sa voice, findVoices()
1180*/
1181void QTextToSpeech::setLocale(const QLocale &locale)
1182{
1183 Q_D(QTextToSpeech);
1184 if (!d->m_engine)
1185 return;
1186
1187 if (d->m_engine->locale() == locale)
1188 return;
1189
1190 const QVoice oldVoice = voice();
1191 if (d->m_engine->setLocale(locale)) {
1192 emit localeChanged(locale);
1193 if (const QVoice newVoice = d->m_engine->voice(); oldVoice != newVoice)
1194 emit voiceChanged(voice: newVoice);
1195 }
1196}
1197
1198QLocale QTextToSpeech::locale() const
1199{
1200 Q_D(const QTextToSpeech);
1201 if (d->m_engine)
1202 return d->m_engine->locale();
1203 return QLocale();
1204}
1205
1206/*!
1207 \qmlmethod list<Voice> TextToSpeech::availableLocales()
1208
1209 Holds the list of locales that are supported by the active \l engine.
1210*/
1211
1212/*!
1213 \return the list of locales that are supported by the active \l engine.
1214
1215 \sa availableVoices(), findVoices()
1216*/
1217QList<QLocale> QTextToSpeech::availableLocales() const
1218{
1219 Q_D(const QTextToSpeech);
1220 if (d->m_engine)
1221 return d->m_engine->availableLocales();
1222 return QList<QLocale>();
1223}
1224
1225/*!
1226 \qmlproperty Voice TextToSpeech::voice
1227
1228 \brief This property holds the voice that will be used for the speech.
1229
1230 The voice needs to be one of the \l{availableVoices()}{voices available} for
1231 the engine.
1232
1233 On some platforms, setting the voice changes other voice attributes such
1234 as \l locale, \l pitch, and so on. These changes trigger the emission of
1235 signals.
1236*/
1237
1238/*!
1239 \property QTextToSpeech::voice
1240 \brief the voice that will be used for the speech.
1241
1242 The voice needs to be one of the \l{availableVoices()}{voices available} for
1243 the engine.
1244
1245 On some platforms, setting the voice changes other voice attributes such
1246 as \l locale, \l pitch, and so on. These changes trigger the emission of
1247 signals.
1248
1249 \sa findVoices()
1250*/
1251void QTextToSpeech::setVoice(const QVoice &voice)
1252{
1253 Q_D(QTextToSpeech);
1254 if (!d->m_engine)
1255 return;
1256
1257 if (d->m_engine->voice() == voice)
1258 return;
1259
1260 const QLocale oldLocale = locale();
1261 if (d->m_engine->setVoice(voice)) {
1262 emit voiceChanged(voice);
1263 if (const QLocale newLocale = d->m_engine->locale(); newLocale != oldLocale)
1264 emit localeChanged(locale: newLocale);
1265 }
1266}
1267
1268QVoice QTextToSpeech::voice() const
1269{
1270 Q_D(const QTextToSpeech);
1271 if (d->m_engine)
1272 return d->m_engine->voice();
1273 return QVoice();
1274}
1275
1276/*!
1277 \qmlmethod list<Voice> TextToSpeech::availableVoices()
1278
1279 Holds the list of voices available for the current \l locale.
1280*/
1281
1282/*!
1283 \return the list of voices available for the current \l locale.
1284
1285 \note If no locale has been set, the system locale is used.
1286
1287 \sa availableLocales(), findVoices()
1288*/
1289QList<QVoice> QTextToSpeech::availableVoices() const
1290{
1291 Q_D(const QTextToSpeech);
1292 if (d->m_engine)
1293 return d->m_engine->availableVoices();
1294 return QList<QVoice>();
1295}
1296
1297/*!
1298 \fn template<typename ...Args> QList<QVoice> QTextToSpeech::findVoices(Args &&...args) const
1299 \since 6.6
1300
1301 \return the list of voices that match the criteria in \a args.
1302
1303 The arguments in \a args are processed in order to assemble the list of voices that
1304 match all of them. An argument of type QString is matched against the \l{QVoice::}{name},
1305 of the voice, an argument of type QLocale is matched agains the voice's
1306 \l {QVoice::}{locale}, etc. It is possible to specify only the \l {QLocale::}{Language} or
1307 \l {QLocale::}{Territory} of the desired voices, and the name can be matched against a
1308 \l {QRegularExpression}{regular expression}.
1309
1310 This function returns all voices if the list of criteria is empty. Multiple criteria
1311 of the same type are not possible and will result in a compile-time error.
1312
1313 \note Unless \a args includes the current \l {QTextToSpeech::}{locale}, this function
1314 might need to change the locale of the engine to get the list of all voices. This is
1315 engine specific, but might impact ongoing speech synthesis. It is therefore advisable
1316 to not call this function unless the \l {QTextToSpeech::}{state} is
1317 \l {QTextToSpeech::State}{Ready}.
1318
1319 \sa availableVoices()
1320*/
1321
1322/*!
1323 \qmlmethod list<voice> TextToSpeech::findVoices(map criteria)
1324 \since 6.6
1325
1326 Returns the list of voices that match all the specified \a criteria.
1327
1328 \a criteria is a map from voice property name to property value, supporting
1329 combinations of search criteria such as:
1330
1331 \code
1332 let daniel = tts.findVoices({
1333 "name": "Daniel"
1334 })
1335 let maleEnglish = tts.findVoices({
1336 "gender": Voice.Male,
1337 "language": Qt.locale('en')
1338 })
1339 \endcode
1340
1341 \sa VoiceSelector
1342*/
1343
1344/*!
1345 \internal
1346
1347 Returns the list of all voices. This requires iterating through all locales,
1348 unless \a locale is set, in which case we can just go through the voices for
1349 that locale.
1350*/
1351QList<QVoice> QTextToSpeech::allVoices(const QLocale *locale) const
1352{
1353 Q_D(const QTextToSpeech);
1354 if (!d->m_engine)
1355 return {};
1356
1357 const QVoice oldVoice = d->m_engine->voice();
1358
1359 QList<QVoice> voices;
1360 QSignalBlocker blockSignals(const_cast<QTextToSpeech *>(this));
1361 const QList<QLocale> allLocales = locale ? QList<QLocale>{*locale} : availableLocales();
1362 for (const auto &l : allLocales) {
1363 if (d->m_engine->locale() != l)
1364 d->m_engine->setLocale(l);
1365 voices << d->m_engine->availableVoices();
1366 }
1367
1368 // reset back to old voice, which will have changed when we changed the
1369 // engine's locale.
1370 if (d->m_engine->voice() != oldVoice)
1371 d->m_engine->setVoice(oldVoice);
1372
1373 return voices;
1374}
1375
1376QT_END_NAMESPACE
1377

source code of qtspeech/src/tts/qtexttospeech.cpp