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
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::errorOccured(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 if (d->m_engine->state() == QTextToSpeech::Speaking) {
830 d->m_pendingUtterances.enqueue(t: utterance);
831 } else {
832 emit aboutToSynthesize(id: 0);
833 d->m_engine->say(text: utterance);
834 }
835
836 return d->m_utteranceCounter++;
837}
838
839/*!
840 \fn template<typename Functor> void QTextToSpeech::synthesize(
841 const QString &text, Functor &&functor)
842 \fn template<typename Functor> void QTextToSpeech::synthesize(
843 const QString &text, const QObject *context, Functor &&functor)
844 \since 6.6
845
846 Synthesizes the \a text into raw audio data.
847
848 This function synthesizes the speech asynchronously into raw audio data.
849 When data is available, the \a functor will be called as
850 \c {functor(QAudioFormat format, QByteArray bytes)}, with \c format
851 describing the \l {QAudioFormat}{format} of the data in \c bytes;
852 or as \c {functor(QAudioBuffer &buffer)}.
853
854 The \l state property is set to \l Synthesizing when the synthesis starts,
855 and to \l Ready once the synthesis is finished. While synthesizing, the
856 \a functor might be called multiple times, possibly with changing values
857 for \c format.
858
859 The \a functor can be a callable, like a lambda or free function, with an
860 optional \a context object:
861
862 \code
863 tts.synthesize("Hello world", [](const QAudioFormat &format, const QByteArray &bytes){
864 // process data according to format
865 });
866 \endcode
867
868 or a member function of the \a context object:
869
870 \code
871 struct PCMProcessor : QObject
872 {
873 void processData(const QAudioFormat &format, const QByteArray &bytes)
874 {
875 // process data according to format
876 }
877 } processor;
878 tts.synthesize("Hello world", &processor, &PCMProcessor::processData);
879 \endcode
880
881 If \a context is destroyed, then the \a functor will no longer get called.
882
883 \note This API requires that the engine has the
884 \l {QTextToSpeech::Capability::}{Synthesize} capability.
885
886 \sa say(), stop()
887*/
888
889/*!
890 \internal
891
892 Handles the engine's synthesized() signal to call \a slotObj on the \a context
893 object. The slot object and the temporary connection are stored and released
894 in updateState() when the state of the engine transitions back to Ready.
895*/
896void QTextToSpeech::synthesizeImpl(const QString &text,
897 QtPrivate::QSlotObjectBase *slotObj, const QObject *context,
898 SynthesizeOverload overload)
899{
900 Q_D(QTextToSpeech);
901 Q_ASSERT(slotObj);
902 if (d->m_slotObject)
903 d->m_slotObject->destroyIfLastRef();
904 d->m_slotObject = slotObj;
905 const auto receive = [d, context, overload](const QAudioFormat &format, const QByteArray &bytes){
906 Q_ASSERT(d->m_slotObject);
907 if (overload == SynthesizeOverload::AudioBuffer) {
908 const QAudioBuffer buffer(bytes, format);
909 void *args[] = {nullptr, const_cast<QAudioBuffer *>(&buffer)};
910 d->m_slotObject->call(r: const_cast<QObject *>(context), a: args);
911 } else {
912 void *args[] = {nullptr,
913 const_cast<QAudioFormat *>(&format),
914 const_cast<QByteArray *>(&bytes)};
915 d->m_slotObject->call(r: const_cast<QObject *>(context), a: args);
916 }
917 };
918 d->m_synthesizeConnection = connect(sender: d->m_engine.get(), signal: &QTextToSpeechEngine::synthesized,
919 context: context ? context : this, slot: receive);
920
921 if (!d->m_engine)
922 return;
923
924 if (d->m_engine->state() == QTextToSpeech::Synthesizing)
925 d->m_pendingUtterances.enqueue(t: text);
926 else
927 d->m_engine->synthesize(text);
928}
929
930/*!
931 \qmlmethod TextToSpeech::stop(BoundaryHint boundaryHint)
932
933 Stops the current reading at \a boundaryHint, and clears the
934 queue of pending texts.
935
936 The reading cannot be resumed. Whether the \a boundaryHint is
937 respected depends on the engine.
938
939 \sa say(), enqueue(), pause(), QTextToSpeech::BoundaryHint
940*/
941
942/*!
943 Stops the current reading at \a boundaryHint, and clears the
944 queue of pending texts.
945
946 The reading cannot be resumed. Whether the \a boundaryHint is
947 respected depends on the engine.
948
949 \sa say(), enqueue(), pause()
950*/
951void QTextToSpeech::stop(BoundaryHint boundaryHint)
952{
953 Q_D(QTextToSpeech);
954 d->m_pendingUtterances = {};
955 d->m_utteranceCounter = 0;
956 if (d->m_engine) {
957 if (boundaryHint == QTextToSpeech::BoundaryHint::Immediate)
958 d->disconnectSynthesizeFunctor();
959 d->m_engine->stop(boundaryHint);
960 }
961}
962
963/*!
964 \qmlmethod TextToSpeech::pause(BoundaryHint boundaryHint)
965
966 Pauses the current speech at \a boundaryHint.
967
968 Whether the \a boundaryHint is respected depends on the \l engine.
969
970 \sa resume(), QTextToSpeech::BoundaryHint,
971 {QTextToSpeech::Capabilities}{PauseResume}
972*/
973
974/*!
975 Pauses the current speech at \a boundaryHint.
976
977 Whether the \a boundaryHint is respected depends on the \l engine.
978
979 \sa resume(), {QTextToSpeech::Capabilities}{PauseResume}
980*/
981void QTextToSpeech::pause(BoundaryHint boundaryHint)
982{
983 Q_D(QTextToSpeech);
984 if (!d->m_engine || d->m_state != QTextToSpeech::Speaking)
985 return;
986
987 if (boundaryHint == BoundaryHint::Utterance) {
988 if (d->m_pendingUtterances.isEmpty() || !d->m_pendingUtterances.first().isEmpty())
989 d->m_pendingUtterances.prepend(t: QString());
990 }
991 // pause called in response to aboutToSynthesize
992 if (d->m_engine->state() == QTextToSpeech::Ready) {
993 d->updateState(newState: QTextToSpeech::Paused);
994 } else {
995 d->m_engine->pause(boundaryHint);
996 }
997}
998
999/*!
1000 \qmlmethod TextToSpeech::resume()
1001
1002 Resume speaking after \l pause() has been called.
1003
1004 \sa pause()
1005*/
1006
1007/*!
1008 Resume speaking after \l pause() has been called.
1009
1010 \note On Android, resuming paused speech will restart from the beginning.
1011 This is a limitation of the underlying text-to-speech engine.
1012
1013 \sa pause()
1014*/
1015void QTextToSpeech::resume()
1016{
1017 Q_D(QTextToSpeech);
1018 if (d->m_state != QTextToSpeech::Paused)
1019 return;
1020
1021 if (d->m_engine) {
1022 // If we are pausing before proceeding with the next utterance,
1023 // then continue with the next pending text.
1024 if (d->m_engine->state() == QTextToSpeech::Ready)
1025 d->updateState(newState: QTextToSpeech::Ready);
1026 else
1027 d->m_engine->resume();
1028 }
1029}
1030
1031/*!
1032 \qmlproperty double TextToSpeech::pitch
1033
1034 This property hold the voice pitch, ranging from -1.0 to 1.0.
1035
1036 The default of 0.0 is the normal speech pitch.
1037*/
1038
1039/*!
1040 \property QTextToSpeech::pitch
1041 \brief the voice pitch, ranging from -1.0 to 1.0.
1042
1043 The default of 0.0 is the normal speech pitch.
1044*/
1045
1046void QTextToSpeech::setPitch(double pitch)
1047{
1048 Q_D(QTextToSpeech);
1049 pitch = qBound(min: -1.0, val: pitch, max: 1.0);
1050
1051 if (!d->m_engine) {
1052 if (d->m_storedPitch != pitch) {
1053 d->m_storedPitch = pitch;
1054 emit pitchChanged(pitch);
1055 }
1056 return;
1057 }
1058 if (d->m_engine->pitch() == pitch)
1059 return;
1060 if (d->m_engine && d->m_engine->setPitch(pitch))
1061 emit pitchChanged(pitch);
1062}
1063
1064double QTextToSpeech::pitch() const
1065{
1066 Q_D(const QTextToSpeech);
1067 if (d->m_engine)
1068 return d->m_engine->pitch();
1069 return qIsNaN(d: d->m_storedPitch) ? 0.0 : d->m_storedPitch;
1070}
1071
1072/*!
1073 \qmlproperty double TextToSpeech::rate
1074
1075 \brief This property holds the current voice rate, ranging from -1.0 to 1.0.
1076
1077 The default of 0.0 is the normal speech flow.
1078*/
1079
1080/*!
1081 \property QTextToSpeech::rate
1082 \brief the current voice rate, ranging from -1.0 to 1.0.
1083
1084 The default value of 0.0 is normal speech flow.
1085*/
1086void QTextToSpeech::setRate(double rate)
1087{
1088 Q_D(QTextToSpeech);
1089 rate = qBound(min: -1.0, val: rate, max: 1.0);
1090
1091 if (!d->m_engine) {
1092 if (d->m_storedRate != rate) {
1093 d->m_storedRate = rate;
1094 emit rateChanged(rate);
1095 }
1096 return;
1097 }
1098 if (d->m_engine->rate() == rate)
1099 return;
1100 if (d->m_engine && d->m_engine->setRate(rate))
1101 emit rateChanged(rate);
1102}
1103
1104double QTextToSpeech::rate() const
1105{
1106 Q_D(const QTextToSpeech);
1107 if (d->m_engine)
1108 return d->m_engine->rate();
1109 return qIsNaN(d: d->m_storedRate) ? 0.0 : d->m_storedRate;
1110}
1111
1112/*!
1113 \qmlproperty double TextToSpeech::volume
1114
1115 \brief This property holds the current volume, ranging from 0.0 to 1.0.
1116
1117 The default value is the platform's default volume.
1118*/
1119
1120/*!
1121 \property QTextToSpeech::volume
1122 \brief the current volume, ranging from 0.0 to 1.0.
1123
1124 The default value is the platform's default volume.
1125*/
1126void QTextToSpeech::setVolume(double volume)
1127{
1128 Q_D(QTextToSpeech);
1129 volume = qBound(min: 0.0, val: volume, max: 1.0);
1130
1131 if (!d->m_engine) {
1132 if (d->m_storedVolume != volume) {
1133 d->m_storedVolume = volume;
1134 emit volumeChanged(volume);
1135 }
1136 return;
1137 }
1138 if (d->m_engine->volume() == volume)
1139 return;
1140 if (d->m_engine->setVolume(volume))
1141 emit volumeChanged(volume);
1142}
1143
1144double QTextToSpeech::volume() const
1145{
1146 Q_D(const QTextToSpeech);
1147 if (d->m_engine)
1148 return d->m_engine->volume();
1149 return qIsNaN(d: d->m_storedVolume) ? 0.0 : d->m_storedVolume;
1150}
1151
1152/*!
1153 \qmlproperty locale TextToSpeech::locale
1154
1155 \brief This property holds the current locale in use.
1156
1157 By default, the system locale is used.
1158
1159 \sa voice
1160*/
1161
1162/*!
1163 \property QTextToSpeech::locale
1164 \brief the current locale in use.
1165
1166 By default, the system locale is used.
1167
1168 On some platforms, changing the locale will update the list of
1169 \l{availableVoices()}{available voices}, and if the current voice is not
1170 available with the new locale, a new voice will be set.
1171
1172 \sa voice, findVoices()
1173*/
1174void QTextToSpeech::setLocale(const QLocale &locale)
1175{
1176 Q_D(QTextToSpeech);
1177 if (!d->m_engine)
1178 return;
1179
1180 if (d->m_engine->locale() == locale)
1181 return;
1182
1183 const QVoice oldVoice = voice();
1184 if (d->m_engine->setLocale(locale)) {
1185 emit localeChanged(locale);
1186 if (const QVoice newVoice = d->m_engine->voice(); oldVoice != newVoice)
1187 emit voiceChanged(voice: newVoice);
1188 }
1189}
1190
1191QLocale QTextToSpeech::locale() const
1192{
1193 Q_D(const QTextToSpeech);
1194 if (d->m_engine)
1195 return d->m_engine->locale();
1196 return QLocale();
1197}
1198
1199/*!
1200 \qmlmethod list<Voice> TextToSpeech::availableLocales()
1201
1202 Holds the list of locales that are supported by the active \l engine.
1203*/
1204
1205/*!
1206 \return the list of locales that are supported by the active \l engine.
1207
1208 \sa availableVoices(), findVoices()
1209*/
1210QList<QLocale> QTextToSpeech::availableLocales() const
1211{
1212 Q_D(const QTextToSpeech);
1213 if (d->m_engine)
1214 return d->m_engine->availableLocales();
1215 return QList<QLocale>();
1216}
1217
1218/*!
1219 \qmlproperty Voice TextToSpeech::voice
1220
1221 \brief This property holds the voice that will be used for the speech.
1222
1223 The voice needs to be one of the \l{availableVoices()}{voices available} for
1224 the engine.
1225
1226 On some platforms, setting the voice changes other voice attributes such
1227 as \l locale, \l pitch, and so on. These changes trigger the emission of
1228 signals.
1229*/
1230
1231/*!
1232 \property QTextToSpeech::voice
1233 \brief the voice that will be used for the speech.
1234
1235 The voice needs to be one of the \l{availableVoices()}{voices available} for
1236 the engine.
1237
1238 On some platforms, setting the voice changes other voice attributes such
1239 as \l locale, \l pitch, and so on. These changes trigger the emission of
1240 signals.
1241
1242 \sa findVoices()
1243*/
1244void QTextToSpeech::setVoice(const QVoice &voice)
1245{
1246 Q_D(QTextToSpeech);
1247 if (!d->m_engine)
1248 return;
1249
1250 if (d->m_engine->voice() == voice)
1251 return;
1252
1253 const QLocale oldLocale = locale();
1254 if (d->m_engine->setVoice(voice)) {
1255 emit voiceChanged(voice);
1256 if (const QLocale newLocale = d->m_engine->locale(); newLocale != oldLocale)
1257 emit localeChanged(locale: newLocale);
1258 }
1259}
1260
1261QVoice QTextToSpeech::voice() const
1262{
1263 Q_D(const QTextToSpeech);
1264 if (d->m_engine)
1265 return d->m_engine->voice();
1266 return QVoice();
1267}
1268
1269/*!
1270 \qmlmethod list<Voice> TextToSpeech::availableVoices()
1271
1272 Holds the list of voices available for the current \l locale.
1273*/
1274
1275/*!
1276 \return the list of voices available for the current \l locale.
1277
1278 \note If no locale has been set, the system locale is used.
1279
1280 \sa availableLocales(), findVoices()
1281*/
1282QList<QVoice> QTextToSpeech::availableVoices() const
1283{
1284 Q_D(const QTextToSpeech);
1285 if (d->m_engine)
1286 return d->m_engine->availableVoices();
1287 return QList<QVoice>();
1288}
1289
1290/*!
1291 \fn template<typename ...Args> QList<QVoice> QTextToSpeech::findVoices(Args &&...args) const
1292 \since 6.6
1293
1294 \return the list of voices that match the criteria in \a args.
1295
1296 The arguments in \a args are processed in order to assemble the list of voices that
1297 match all of them. An argument of type QString is matched against the \l{QVoice::}{name},
1298 of the voice, an argument of type QLocale is matched agains the voice's
1299 \l {QVoice::}{locale}, etc. It is possible to specify only the \l {QLocale::}{Language} or
1300 \l {QLocale::}{Territory} of the desired voices, and the name can be matched against a
1301 \l {QRegularExpression}{regular expression}.
1302
1303 This function returns all voices if the list of criteria is empty. Multiple criteria
1304 of the same type are not possible and will result in a compile-time error.
1305
1306 \note Unless \a args includes the current \l {QTextToSpeech::}{locale}, this function
1307 might need to change the locale of the engine to get the list of all voices. This is
1308 engine specific, but might impact ongoing speech synthesis. It is therefore advisable
1309 to not call this function unless the \l {QTextToSpeech::}{state} is
1310 \l {QTextToSpeech::State}{Ready}.
1311
1312 \sa availableVoices()
1313*/
1314
1315/*!
1316 \qmlmethod list<voice> TextToSpeech::findVoices(map criteria)
1317 \since 6.6
1318
1319 Returns the list of voices that match all the specified \a criteria.
1320
1321 \a criteria is a map from voice property name to property value, supporting
1322 combinations of search criteria such as:
1323
1324 \code
1325 let daniel = tts.findVoices({
1326 "name": "Daniel"
1327 })
1328 let maleEnglish = tts.findVoices({
1329 "gender": Voice.Male,
1330 "language": Qt.locale('en')
1331 })
1332 \endcode
1333
1334 \sa VoiceSelector
1335*/
1336
1337/*!
1338 \internal
1339
1340 Returns the list of all voices. This requires iterating through all locales,
1341 unless \a locale is set, in which case we can just go through the voices for
1342 that locale.
1343*/
1344QList<QVoice> QTextToSpeech::allVoices(const QLocale *locale) const
1345{
1346 Q_D(const QTextToSpeech);
1347 if (!d->m_engine)
1348 return {};
1349
1350 const QVoice oldVoice = d->m_engine->voice();
1351
1352 QList<QVoice> voices;
1353 QSignalBlocker blockSignals(const_cast<QTextToSpeech *>(this));
1354 const QList<QLocale> allLocales = locale ? QList<QLocale>{*locale} : availableLocales();
1355 for (const auto &l : allLocales) {
1356 if (d->m_engine->locale() != l)
1357 d->m_engine->setLocale(l);
1358 voices << d->m_engine->availableVoices();
1359 }
1360
1361 // reset back to old voice, which will have changed when we changed the
1362 // engine's locale.
1363 if (d->m_engine->voice() != oldVoice)
1364 d->m_engine->setVoice(oldVoice);
1365
1366 return voices;
1367}
1368
1369QT_END_NAMESPACE
1370

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