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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | using namespace Qt::StringLiterals; |
17 | |
18 | Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, |
19 | ("org.qt-project.qt.speech.tts.plugin/6.0" , |
20 | QLatin1String("/texttospeech" ))) |
21 | |
22 | QMutex QTextToSpeechPrivate::m_mutex; |
23 | |
24 | QTextToSpeechPrivate::QTextToSpeechPrivate(QTextToSpeech *speech) |
25 | : q_ptr(speech) |
26 | { |
27 | qRegisterMetaType<QTextToSpeech::State>(); |
28 | qRegisterMetaType<QTextToSpeech::ErrorReason>(); |
29 | } |
30 | |
31 | QTextToSpeechPrivate::~QTextToSpeechPrivate() |
32 | { |
33 | } |
34 | |
35 | void QTextToSpeechPrivate::setEngineProvider(const QString &engine, const QVariantMap ¶ms) |
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 | |
92 | bool 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 | |
118 | void 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 | |
128 | QMultiHash<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 | |
144 | void 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 | |
155 | void 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 | |
214 | void 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 | */ |
371 | QTextToSpeech::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 | */ |
389 | QTextToSpeech::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 | */ |
412 | QTextToSpeech::QTextToSpeech(const QString &engine, const QVariantMap ¶ms, 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 | */ |
427 | QTextToSpeech::~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 | */ |
465 | bool QTextToSpeech::setEngine(const QString &engine, const QVariantMap ¶ms) |
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 | |
508 | QString 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 | */ |
545 | QTextToSpeech::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 | */ |
592 | QStringList 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 | */ |
614 | QTextToSpeech::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 | */ |
710 | QTextToSpeech::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 | */ |
728 | QString 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 | */ |
774 | void 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 | */ |
823 | qsizetype 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 | */ |
896 | void 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 | */ |
951 | void 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 | */ |
981 | void 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 | */ |
1015 | void 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 | |
1046 | void 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 | |
1064 | double 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 | */ |
1086 | void 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 | |
1104 | double 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 | */ |
1126 | void 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 | |
1144 | double 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 | */ |
1174 | void 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 | |
1191 | QLocale 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 | */ |
1210 | QList<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 | */ |
1244 | void 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 | |
1261 | QVoice 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 | */ |
1282 | QList<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 | */ |
1344 | QList<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 | |
1369 | QT_END_NAMESPACE |
1370 | |