1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtexttospeech_flite.h"
5
6#include <QtCore/QCoreApplication>
7
8QT_BEGIN_NAMESPACE
9
10using namespace Qt::StringLiterals;
11
12QTextToSpeechEngineFlite::QTextToSpeechEngineFlite(const QVariantMap &parameters, QObject *parent)
13 : QTextToSpeechEngine(parent)
14{
15 QAudioDevice audioDevice;
16 if (const auto it = parameters.find(key: "audioDevice"_L1); it != parameters.end())
17 audioDevice = (*it).value<QAudioDevice>();
18 else
19 audioDevice = QMediaDevices::defaultAudioOutput();
20
21 if (audioDevice.isNull()) {
22 m_errorReason = QTextToSpeech::ErrorReason::Playback;
23 m_errorString = QCoreApplication::translate(context: "QTextToSpeech", key: "No audio device available");
24 }
25 m_processor.reset(p: new QTextToSpeechProcessorFlite(audioDevice));
26
27 // Connect processor to engine for state changes and error
28 connect(sender: m_processor.get(), signal: &QTextToSpeechProcessorFlite::stateChanged,
29 context: this, slot: &QTextToSpeechEngineFlite::changeState);
30 connect(sender: m_processor.get(), signal: &QTextToSpeechProcessorFlite::errorOccurred, context: this,
31 slot: &QTextToSpeechEngineFlite::setError);
32 connect(sender: m_processor.get(), signal: &QTextToSpeechProcessorFlite::sayingWord, context: this,
33 slot: &QTextToSpeechEngine::sayingWord);
34 connect(sender: m_processor.get(), signal: &QTextToSpeechProcessorFlite::synthesized, context: this,
35 slot: &QTextToSpeechEngine::synthesized);
36
37 // Read voices from processor before moving it to a separate thread
38 const QList<QTextToSpeechProcessorFlite::VoiceInfo> voices = m_processor->voices();
39
40 int voiceIndex = 0;
41 for (const QTextToSpeechProcessorFlite::VoiceInfo &voiceInfo : voices) {
42 const QLocale locale(voiceInfo.locale);
43 const QVoice voice = QTextToSpeechEngine::createVoice(name: voiceInfo.name, locale,
44 gender: voiceInfo.gender, age: voiceInfo.age,
45 data: QVariant(voiceInfo.id));
46 m_voices.insert(key: locale, value: voice);
47 // Use the first available locale/voice as a fallback
48 if (voiceIndex == 0)
49 m_voice = voice;
50 ++voiceIndex;
51 }
52
53 if (voiceIndex) {
54 m_state = QTextToSpeech::Ready;
55 m_processor->moveToThread(thread: &m_thread);
56 m_thread.start();
57 } else {
58 m_errorReason = QTextToSpeech::ErrorReason::Configuration;
59 m_errorString = QCoreApplication::translate(context: "QTextToSpeech", key: "No voices available");
60 }
61}
62
63QTextToSpeechEngineFlite::~QTextToSpeechEngineFlite()
64{
65 m_thread.exit();
66 m_thread.wait();
67}
68
69QList<QLocale> QTextToSpeechEngineFlite::availableLocales() const
70{
71 return m_voices.uniqueKeys();
72}
73
74QList<QVoice> QTextToSpeechEngineFlite::availableVoices() const
75{
76 return m_voices.values(key: m_voice.locale());
77}
78
79void QTextToSpeechEngineFlite::say(const QString &text)
80{
81 QMetaObject::invokeMethod(obj: m_processor.get(), member: "say", c: Qt::QueuedConnection, Q_ARG(QString, text),
82 Q_ARG(int, voiceData(voice()).toInt()), Q_ARG(double, pitch()),
83 Q_ARG(double, rate()), Q_ARG(double, volume()));
84}
85
86void QTextToSpeechEngineFlite::synthesize(const QString &text)
87{
88 QMetaObject::invokeMethod(obj: m_processor.get(), member: "synthesize", c: Qt::QueuedConnection, Q_ARG(QString, text),
89 Q_ARG(int, voiceData(voice()).toInt()), Q_ARG(double, pitch()),
90 Q_ARG(double, rate()), Q_ARG(double, volume()));
91}
92
93void QTextToSpeechEngineFlite::stop(QTextToSpeech::BoundaryHint boundaryHint)
94{
95 Q_UNUSED(boundaryHint);
96 QMetaObject::invokeMethod(object: m_processor.get(), function: &QTextToSpeechProcessorFlite::stop, type: Qt::QueuedConnection);
97}
98
99void QTextToSpeechEngineFlite::pause(QTextToSpeech::BoundaryHint boundaryHint)
100{
101 Q_UNUSED(boundaryHint);
102 QMetaObject::invokeMethod(object: m_processor.get(), function: &QTextToSpeechProcessorFlite::pause, type: Qt::QueuedConnection);
103}
104
105void QTextToSpeechEngineFlite::resume()
106{
107 QMetaObject::invokeMethod(object: m_processor.get(), function: &QTextToSpeechProcessorFlite::resume, type: Qt::QueuedConnection);
108}
109
110double QTextToSpeechEngineFlite::rate() const
111{
112 return m_rate;
113}
114
115bool QTextToSpeechEngineFlite::setRate(double rate)
116{
117 if (m_rate == rate)
118 return false;
119
120 m_rate = rate;
121 return true;
122}
123
124double QTextToSpeechEngineFlite::pitch() const
125{
126 return m_pitch;
127}
128
129bool QTextToSpeechEngineFlite::setPitch(double pitch)
130{
131 if (m_pitch == pitch)
132 return false;
133
134 m_pitch = pitch;
135 return true;
136}
137
138QLocale QTextToSpeechEngineFlite::locale() const
139{
140 return m_voice.locale();
141}
142
143bool QTextToSpeechEngineFlite::setLocale(const QLocale &locale)
144{
145 const auto &voices = m_voices.values(key: locale);
146 if (voices.isEmpty())
147 return false;
148 // The list returned by QMultiHash::values is reversed
149 setVoice(voices.last());
150 return true;
151}
152
153double QTextToSpeechEngineFlite::volume() const
154{
155 return m_volume;
156}
157
158bool QTextToSpeechEngineFlite::setVolume(double volume)
159{
160 if (m_volume == volume)
161 return false;
162
163 m_volume = volume;
164 return true;
165}
166
167QVoice QTextToSpeechEngineFlite::voice() const
168{
169 return m_voice;
170}
171
172bool QTextToSpeechEngineFlite::setVoice(const QVoice &voice)
173{
174 QLocale locale = m_voices.key(value: voice); // returns default locale if not found, so
175 if (!m_voices.contains(key: locale, value: voice)) {
176 qWarning() << "Voice" << voice << "is not supported by this engine";
177 return false;
178 }
179
180 m_voice = voice;
181 return true;
182}
183
184void QTextToSpeechEngineFlite::changeState(QTextToSpeech::State newState)
185{
186 if (newState != m_state) {
187 m_state = newState;
188 emit stateChanged(state: newState);
189 }
190}
191
192QTextToSpeech::State QTextToSpeechEngineFlite::state() const
193{
194 return m_state;
195}
196
197QTextToSpeech::ErrorReason QTextToSpeechEngineFlite::errorReason() const
198{
199 return m_errorReason;
200}
201
202QString QTextToSpeechEngineFlite::errorString() const
203{
204 return m_errorString;
205}
206
207void QTextToSpeechEngineFlite::setError(QTextToSpeech::ErrorReason error, const QString &errorString)
208{
209 m_errorReason = error;
210 m_errorString = errorString;
211 changeState(newState: QTextToSpeech::Error);
212 emit errorOccurred(error, errorString);
213}
214
215QT_END_NAMESPACE
216

source code of qtspeech/src/plugins/tts/flite/qtexttospeech_flite.cpp