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 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | class QAudioFormat; |
21 | class QAudioBuffer; |
22 | |
23 | class QTextToSpeechPrivate; |
24 | class 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 | |
37 | public: |
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 ¶ms, |
78 | QObject *parent = nullptr); |
79 | ~QTextToSpeech() override; |
80 | |
81 | Q_INVOKABLE bool setEngine(const QString &engine, const QVariantMap ¶ms = 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 | |
150 | public 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 | |
164 | Q_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 | |
177 | protected: |
178 | QList<QVoice> allVoices(const QLocale *locale) const; |
179 | |
180 | private: |
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 | }; |
244 | Q_DECLARE_OPERATORS_FOR_FLAGS(QTextToSpeech::Capabilities) |
245 | |
246 | QT_END_NAMESPACE |
247 | |
248 | #endif |
249 | |