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: u"audioDevice"_s); 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_thread.setObjectName("QTextToSpeechEngineFlite");
56 m_processor->moveToThread(thread: &m_thread);
57 m_thread.start();
58 } else {
59 m_errorReason = QTextToSpeech::ErrorReason::Configuration;
60 m_errorString = QCoreApplication::translate(context: "QTextToSpeech", key: "No voices available");
61 }
62}
63
64QTextToSpeechEngineFlite::~QTextToSpeechEngineFlite()
65{
66 m_thread.exit();
67 m_thread.wait();
68}
69
70QList<QLocale> QTextToSpeechEngineFlite::availableLocales() const
71{
72 return m_voices.uniqueKeys();
73}
74
75QList<QVoice> QTextToSpeechEngineFlite::availableVoices() const
76{
77 return m_voices.values(key: m_voice.locale());
78}
79
80void QTextToSpeechEngineFlite::say(const QString &text)
81{
82 QMetaObject::invokeMethod(obj: m_processor.get(), member: "say", c: Qt::QueuedConnection, Q_ARG(QString, text),
83 Q_ARG(int, voiceData(voice()).toInt()), Q_ARG(double, pitch()),
84 Q_ARG(double, rate()), Q_ARG(double, volume()));
85}
86
87void QTextToSpeechEngineFlite::synthesize(const QString &text)
88{
89 QMetaObject::invokeMethod(obj: m_processor.get(), member: "synthesize", c: Qt::QueuedConnection, Q_ARG(QString, text),
90 Q_ARG(int, voiceData(voice()).toInt()), Q_ARG(double, pitch()),
91 Q_ARG(double, rate()), Q_ARG(double, volume()));
92}
93
94void QTextToSpeechEngineFlite::stop(QTextToSpeech::BoundaryHint boundaryHint)
95{
96 Q_UNUSED(boundaryHint);
97 QMetaObject::invokeMethod(object: m_processor.get(), function: &QTextToSpeechProcessorFlite::stop, type: Qt::QueuedConnection);
98}
99
100void QTextToSpeechEngineFlite::pause(QTextToSpeech::BoundaryHint boundaryHint)
101{
102 Q_UNUSED(boundaryHint);
103 QMetaObject::invokeMethod(object: m_processor.get(), function: &QTextToSpeechProcessorFlite::pause, type: Qt::QueuedConnection);
104}
105
106void QTextToSpeechEngineFlite::resume()
107{
108 QMetaObject::invokeMethod(object: m_processor.get(), function: &QTextToSpeechProcessorFlite::resume, type: Qt::QueuedConnection);
109}
110
111double QTextToSpeechEngineFlite::rate() const
112{
113 return m_rate;
114}
115
116bool QTextToSpeechEngineFlite::setRate(double rate)
117{
118 if (m_rate == rate)
119 return false;
120
121 m_rate = rate;
122 return true;
123}
124
125double QTextToSpeechEngineFlite::pitch() const
126{
127 return m_pitch;
128}
129
130bool QTextToSpeechEngineFlite::setPitch(double pitch)
131{
132 if (m_pitch == pitch)
133 return false;
134
135 m_pitch = pitch;
136 return true;
137}
138
139QLocale QTextToSpeechEngineFlite::locale() const
140{
141 return m_voice.locale();
142}
143
144bool QTextToSpeechEngineFlite::setLocale(const QLocale &locale)
145{
146 const auto &voices = m_voices.values(key: locale);
147 if (voices.isEmpty())
148 return false;
149 // The list returned by QMultiHash::values is reversed
150 setVoice(voices.last());
151 return true;
152}
153
154double QTextToSpeechEngineFlite::volume() const
155{
156 return m_volume;
157}
158
159bool QTextToSpeechEngineFlite::setVolume(double volume)
160{
161 if (m_volume == volume)
162 return false;
163
164 m_volume = volume;
165 return true;
166}
167
168QVoice QTextToSpeechEngineFlite::voice() const
169{
170 return m_voice;
171}
172
173bool QTextToSpeechEngineFlite::setVoice(const QVoice &voice)
174{
175 QLocale locale = m_voices.key(value: voice); // returns default locale if not found, so
176 if (!m_voices.contains(key: locale, value: voice)) {
177 qWarning() << "Voice" << voice << "is not supported by this engine";
178 return false;
179 }
180
181 m_voice = voice;
182 return true;
183}
184
185void QTextToSpeechEngineFlite::changeState(QTextToSpeech::State newState)
186{
187 if (newState != m_state) {
188 m_state = newState;
189 emit stateChanged(state: newState);
190 }
191}
192
193QTextToSpeech::State QTextToSpeechEngineFlite::state() const
194{
195 return m_state;
196}
197
198QTextToSpeech::ErrorReason QTextToSpeechEngineFlite::errorReason() const
199{
200 return m_errorReason;
201}
202
203QString QTextToSpeechEngineFlite::errorString() const
204{
205 return m_errorString;
206}
207
208void QTextToSpeechEngineFlite::setError(QTextToSpeech::ErrorReason error, const QString &errorString)
209{
210 m_errorReason = error;
211 m_errorString = errorString;
212 changeState(newState: QTextToSpeech::Error);
213 emit errorOccurred(error, errorString);
214}
215
216QT_END_NAMESPACE
217

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