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

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