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
6
7#ifndef QTEXTTOSPEECH_H
8#define QTEXTTOSPEECH_H
9
10#include <QtTextToSpeech/qtexttospeech_global.h>
11#include <QtTextToSpeech/qvoice.h>
12#include <QtCore/qobject.h>
13#include <QtCore/qshareddata.h>
14#include <QtCore/qlocale.h>
15
16#include <QtCore/q20type_traits.h>
17
18QT_BEGIN_NAMESPACE
19
20class QAudioFormat;
21class QAudioBuffer;
22
23class QTextToSpeechPrivate;
24class Q_TEXTTOSPEECH_EXPORT QTextToSpeech : public QObject
25{
26 Q_OBJECT
27 Q_PROPERTY(QString engine READ engine WRITE setEngine NOTIFY engineChanged)
28 Q_PROPERTY(State state READ state NOTIFY stateChanged FINAL)
29 Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged FINAL)
30 Q_PROPERTY(double rate READ rate WRITE setRate NOTIFY rateChanged FINAL)
31 Q_PROPERTY(double pitch READ pitch WRITE setPitch NOTIFY pitchChanged FINAL)
32 Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged FINAL)
33 Q_PROPERTY(QVoice voice READ voice WRITE setVoice NOTIFY voiceChanged FINAL)
34 Q_PROPERTY(Capabilities engineCapabilities READ engineCapabilities NOTIFY engineChanged REVISION(6, 6) FINAL)
35 Q_DECLARE_PRIVATE(QTextToSpeech)
36
37public:
38 enum State {
39 Ready,
40 Speaking,
41 Paused,
42 Error,
43 Synthesizing,
44 };
45 Q_ENUM(State)
46
47 enum class ErrorReason {
48 NoError,
49 Initialization,
50 Configuration,
51 Input,
52 Playback,
53 };
54 Q_ENUM(ErrorReason)
55
56 enum class BoundaryHint {
57 Default,
58 Immediate,
59 Word,
60 Sentence,
61 Utterance,
62 };
63 Q_ENUM(BoundaryHint)
64
65 enum class Capability {
66 None = 0,
67 Speak = 1 << 0,
68 PauseResume = 1 << 1,
69 WordByWordProgress = 1 << 2,
70 Synthesize = 1 << 3,
71 };
72 Q_DECLARE_FLAGS(Capabilities, Capability)
73 Q_FLAG(Capabilities)
74
75 explicit QTextToSpeech(QObject *parent = nullptr);
76 explicit QTextToSpeech(const QString &engine, QObject *parent = nullptr);
77 explicit QTextToSpeech(const QString &engine, const QVariantMap &params,
78 QObject *parent = nullptr);
79 ~QTextToSpeech() override;
80
81 Q_INVOKABLE bool setEngine(const QString &engine, const QVariantMap &params = QVariantMap());
82 QString engine() const;
83 QTextToSpeech::Capabilities engineCapabilities() const;
84
85 QTextToSpeech::State state() const;
86 Q_INVOKABLE QTextToSpeech::ErrorReason errorReason() const;
87 Q_INVOKABLE QString errorString() const;
88
89 Q_INVOKABLE QList<QLocale> availableLocales() const;
90 QLocale locale() const;
91
92 QVoice voice() const;
93 Q_INVOKABLE QList<QVoice> availableVoices() const;
94
95 double rate() const;
96 double pitch() const;
97 double volume() const;
98
99 Q_INVOKABLE static QStringList availableEngines();
100
101 template <typename Functor>
102 void synthesize(const QString &text,
103#ifdef Q_QDOC
104 const QObject *receiver,
105#else
106 const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
107# endif // Q_QDOC
108 Functor &&func)
109 {
110 using Prototype2 = void(*)(QAudioFormat, QByteArray);
111 using Prototype1 = void(*)(QAudioBuffer);
112 if constexpr (qxp::is_detected_v<CompatibleCallbackTest2, Functor>) {
113 synthesizeImpl(text, slotObj: QtPrivate::makeCallableObject<Prototype2>(std::forward<Functor>(func)),
114 context: receiver, overload: SynthesizeOverload::AudioFormatByteArray);
115 } else if constexpr (qxp::is_detected_v<CompatibleCallbackTest1, Functor>) {
116 synthesizeImpl(text, slotObj: QtPrivate::makeCallableObject<Prototype1>(std::forward<Functor>(func)),
117 context: receiver, overload: SynthesizeOverload::AudioBuffer);
118 } else {
119 static_assert(QtPrivate::type_dependent_false<Functor>(),
120 "Incompatible functor signature, must be either "
121 "(QAudioFormat, QByteArray) or (QAudioBuffer)!");
122 }
123 }
124
125 // synthesize to a functor or function pointer (without context)
126 template <typename Functor>
127 void synthesize(const QString &text, Functor &&func)
128 {
129 synthesize(text, nullptr, std::forward<Functor>(func));
130 }
131
132 template <typename ...Args>
133 QList<QVoice> findVoices(Args &&...args) const
134 {
135 // if any of the arguments is a locale, then we can avoid iterating through all
136 // and only have to search through the voices for that locale.
137 QLocale locale;
138 QLocale *plocale = nullptr;
139 if constexpr (std::disjunction_v<std::is_same<q20::remove_cvref_t<Args>, QLocale>...>) {
140 locale = std::get<QLocale>(std::make_tuple(args...));
141 plocale = &locale;
142 }
143
144 auto voices = allVoices(locale: plocale);
145 if constexpr (sizeof...(args) > 0)
146 voices.removeIf([&](const QVoice &voice) -> bool { return !voiceMatches(voice, args...); });
147 return voices;
148 }
149
150public Q_SLOTS:
151 void say(const QString &text);
152 qsizetype enqueue(const QString &text);
153 void stop(QTextToSpeech::BoundaryHint boundaryHint = QTextToSpeech::BoundaryHint::Default);
154 void pause(QTextToSpeech::BoundaryHint boundaryHint = QTextToSpeech::BoundaryHint::Default);
155 void resume();
156
157 void setLocale(const QLocale &locale);
158
159 void setRate(double rate);
160 void setPitch(double pitch);
161 void setVolume(double volume);
162 void setVoice(const QVoice &voice);
163
164Q_SIGNALS:
165 void engineChanged(const QString &engine);
166 void stateChanged(QTextToSpeech::State state);
167 void errorOccurred(QTextToSpeech::ErrorReason error, const QString &errorString);
168 void localeChanged(const QLocale &locale);
169 void rateChanged(double rate);
170 void pitchChanged(double pitch);
171 void volumeChanged(double volume);
172 void voiceChanged(const QVoice &voice);
173
174 void sayingWord(const QString &word, qsizetype id, qsizetype start, qsizetype length);
175 void aboutToSynthesize(qsizetype id);
176
177protected:
178 QList<QVoice> allVoices(const QLocale *locale) const;
179
180private:
181 template <typename Functor>
182 using CompatibleCallbackTest2 = decltype(QtPrivate::makeCallableObject<void(*)(QAudioFormat, QByteArray)>(std::declval<Functor>()));
183 template <typename Functor>
184 using CompatibleCallbackTest1 = decltype(QtPrivate::makeCallableObject<void(*)(QAudioBuffer)>(std::declval<Functor>()));
185
186 enum class SynthesizeOverload {
187 AudioFormatByteArray,
188 AudioBuffer
189 };
190
191 void synthesizeImpl(const QString &text,
192 QtPrivate::QSlotObjectBase *slotObj, const QObject *context,
193 SynthesizeOverload overload);
194
195 // Helper type to find the index of a type in a tuple, which allows
196 // us to generate a compile-time error if there are multiple criteria
197 // of the same type.
198 template <typename T, typename Tuple> struct LastIndexOf;
199 template <typename T, typename ...Ts>
200 struct LastIndexOf<T, std::tuple<Ts...>> {
201 template <qsizetype... Is>
202 static constexpr qsizetype lastIndexOf(std::integer_sequence<qsizetype, Is...>) {
203 return std::max({(std::is_same_v<T, Ts> ? Is : -1)...});
204 }
205 static constexpr qsizetype value =
206 lastIndexOf(std::make_integer_sequence<qsizetype, sizeof...(Ts)>{});
207 };
208
209 template <typename Arg0, typename ...Args>
210 bool voiceMatches(const QVoice &voice, Arg0 &&arg0, Args &&...args) const {
211 using ArgType = q20::remove_cvref_t<Arg0>;
212 bool matches = [&]{
213 if constexpr (std::is_same_v<ArgType, QLocale>) {
214 return voice.locale() == arg0;
215 } else if constexpr (std::is_same_v<ArgType, QLocale::Language>) {
216 return voice.locale().language() == arg0;
217 } else if constexpr (std::is_same_v<ArgType, QLocale::Territory>) {
218 return voice.locale().territory() == arg0;
219 } else if constexpr (std::is_same_v<ArgType, QVoice::Gender>) {
220 return voice.gender() == arg0;
221 } else if constexpr (std::is_same_v<ArgType, QVoice::Age>) {
222 return voice.age() == arg0;
223 } else if constexpr (std::disjunction_v<std::is_convertible<ArgType, QString>,
224 std::is_convertible<ArgType, QStringView>>) {
225 return voice.name() == arg0;
226 } else if constexpr (std::is_same_v<ArgType, QRegularExpression>) {
227 return arg0.match(voice.name()).hasMatch();
228 } else {
229 static_assert(QtPrivate::type_dependent_false<Arg0>(),
230 "Type cannot be matched to a QVoice property!");
231 return false;
232 }
233 }();
234 if constexpr (sizeof...(args) > 0) {
235 static_assert(LastIndexOf<ArgType, std::tuple<q20::remove_cvref_t<Args>...>>::value == -1,
236 "Using multiple criteria of the same type is not supported");
237 matches = matches && voiceMatches(voice, args...);
238 }
239 return matches;
240 }
241
242 Q_DISABLE_COPY(QTextToSpeech)
243};
244Q_DECLARE_OPERATORS_FOR_FLAGS(QTextToSpeech::Capabilities)
245
246QT_END_NAMESPACE
247
248#endif
249

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