| 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 OR GPL-3.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 | |