// Copyright (C) 2015 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QTEXTTOSPEECH_H #define QTEXTTOSPEECH_H #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QAudioFormat; class QAudioBuffer; class QTextToSpeechPrivate; class Q_TEXTTOSPEECH_EXPORT QTextToSpeech : public QObject { Q_OBJECT Q_PROPERTY(QString engine READ engine WRITE setEngine NOTIFY engineChanged) Q_PROPERTY(State state READ state NOTIFY stateChanged FINAL) Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged FINAL) Q_PROPERTY(double rate READ rate WRITE setRate NOTIFY rateChanged FINAL) Q_PROPERTY(double pitch READ pitch WRITE setPitch NOTIFY pitchChanged FINAL) Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged FINAL) Q_PROPERTY(QVoice voice READ voice WRITE setVoice NOTIFY voiceChanged FINAL) Q_PROPERTY(Capabilities engineCapabilities READ engineCapabilities NOTIFY engineChanged REVISION(6, 6) FINAL) Q_DECLARE_PRIVATE(QTextToSpeech) public: enum State { Ready, Speaking, Paused, Error, Synthesizing, }; Q_ENUM(State) enum class ErrorReason { NoError, Initialization, Configuration, Input, Playback, }; Q_ENUM(ErrorReason) enum class BoundaryHint { Default, Immediate, Word, Sentence, Utterance, }; Q_ENUM(BoundaryHint) enum class Capability { None = 0, Speak = 1 << 0, PauseResume = 1 << 1, WordByWordProgress = 1 << 2, Synthesize = 1 << 3, }; Q_DECLARE_FLAGS(Capabilities, Capability) Q_FLAG(Capabilities) explicit QTextToSpeech(QObject *parent = nullptr); explicit QTextToSpeech(const QString &engine, QObject *parent = nullptr); explicit QTextToSpeech(const QString &engine, const QVariantMap ¶ms, QObject *parent = nullptr); ~QTextToSpeech() override; Q_INVOKABLE bool setEngine(const QString &engine, const QVariantMap ¶ms = QVariantMap()); QString engine() const; QTextToSpeech::Capabilities engineCapabilities() const; QTextToSpeech::State state() const; Q_INVOKABLE QTextToSpeech::ErrorReason errorReason() const; Q_INVOKABLE QString errorString() const; Q_INVOKABLE QList availableLocales() const; QLocale locale() const; QVoice voice() const; Q_INVOKABLE QList availableVoices() const; double rate() const; double pitch() const; double volume() const; Q_INVOKABLE static QStringList availableEngines(); template void synthesize(const QString &text, #ifdef Q_QDOC const QObject *receiver, #else const typename QtPrivate::ContextTypeForFunctor::ContextType *receiver, # endif // Q_QDOC Functor &&func) { using Prototype2 = void(*)(QAudioFormat, QByteArray); using Prototype1 = void(*)(QAudioBuffer); if constexpr (qxp::is_detected_v) { synthesizeImpl(text, QtPrivate::makeCallableObject(std::forward(func)), receiver, SynthesizeOverload::AudioFormatByteArray); } else if constexpr (qxp::is_detected_v) { synthesizeImpl(text, QtPrivate::makeCallableObject(std::forward(func)), receiver, SynthesizeOverload::AudioBuffer); } else { static_assert(QtPrivate::type_dependent_false(), "Incompatible functor signature, must be either " "(QAudioFormat, QByteArray) or (QAudioBuffer)!"); } } // synthesize to a functor or function pointer (without context) template void synthesize(const QString &text, Functor &&func) { synthesize(text, nullptr, std::forward(func)); } template QList findVoices(Args &&...args) const { // if any of the arguments is a locale, then we can avoid iterating through all // and only have to search through the voices for that locale. QLocale locale; QLocale *plocale = nullptr; if constexpr (std::disjunction_v, QLocale>...>) { locale = std::get(std::make_tuple(args...)); plocale = &locale; } auto voices = allVoices(plocale); if constexpr (sizeof...(args) > 0) voices.removeIf([&](const QVoice &voice) -> bool { return !voiceMatches(voice, args...); }); return voices; } public Q_SLOTS: void say(const QString &text); qsizetype enqueue(const QString &text); void stop(QTextToSpeech::BoundaryHint boundaryHint = QTextToSpeech::BoundaryHint::Default); void pause(QTextToSpeech::BoundaryHint boundaryHint = QTextToSpeech::BoundaryHint::Default); void resume(); void setLocale(const QLocale &locale); void setRate(double rate); void setPitch(double pitch); void setVolume(double volume); void setVoice(const QVoice &voice); Q_SIGNALS: void engineChanged(const QString &engine); void stateChanged(QTextToSpeech::State state); void errorOccurred(QTextToSpeech::ErrorReason error, const QString &errorString); void localeChanged(const QLocale &locale); void rateChanged(double rate); void pitchChanged(double pitch); void volumeChanged(double volume); void voiceChanged(const QVoice &voice); void sayingWord(const QString &word, qsizetype id, qsizetype start, qsizetype length); void aboutToSynthesize(qsizetype id); protected: QList allVoices(const QLocale *locale) const; private: template using CompatibleCallbackTest2 = decltype(QtPrivate::makeCallableObject(std::declval())); template using CompatibleCallbackTest1 = decltype(QtPrivate::makeCallableObject(std::declval())); enum class SynthesizeOverload { AudioFormatByteArray, AudioBuffer }; void synthesizeImpl(const QString &text, QtPrivate::QSlotObjectBase *slotObj, const QObject *context, SynthesizeOverload overload); // Helper type to find the index of a type in a tuple, which allows // us to generate a compile-time error if there are multiple criteria // of the same type. template struct LastIndexOf; template struct LastIndexOf> { template static constexpr qsizetype lastIndexOf(std::integer_sequence) { return std::max({(std::is_same_v ? Is : -1)...}); } static constexpr qsizetype value = lastIndexOf(std::make_integer_sequence{}); }; template bool voiceMatches(const QVoice &voice, Arg0 &&arg0, Args &&...args) const { using ArgType = q20::remove_cvref_t; bool matches = [&]{ if constexpr (std::is_same_v) { return voice.locale() == arg0; } else if constexpr (std::is_same_v) { return voice.locale().language() == arg0; } else if constexpr (std::is_same_v) { return voice.locale().territory() == arg0; } else if constexpr (std::is_same_v) { return voice.gender() == arg0; } else if constexpr (std::is_same_v) { return voice.age() == arg0; } else if constexpr (std::disjunction_v, std::is_convertible>) { return voice.name() == arg0; } else if constexpr (std::is_same_v) { return arg0.match(voice.name()).hasMatch(); } else { static_assert(QtPrivate::type_dependent_false(), "Type cannot be matched to a QVoice property!"); return false; } }(); if constexpr (sizeof...(args) > 0) { static_assert(LastIndexOf...>>::value == -1, "Using multiple criteria of the same type is not supported"); matches = matches && voiceMatches(voice, args...); } return matches; } Q_DISABLE_COPY(QTextToSpeech) }; Q_DECLARE_OPERATORS_FOR_FLAGS(QTextToSpeech::Capabilities) QT_END_NAMESPACE #endif