Source code
Revision control
Copy as Markdown
// 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
// Qt-Security score:significant reason:default
#ifndef QTEXTTOSPEECH_H
#define QTEXTTOSPEECH_H
#include <QtCore/qlocale.h>
#include <QtCore/qobject.h>
#include <QtCore/qmap.h>
#include <QtCore/qshareddata.h>
#include <QtTextToSpeech/qtexttospeech_global.h>
#include <QtTextToSpeech/qvoice.h>
#include <QtCore/q20type_traits.h>
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<QLocale> availableLocales() const;
QLocale locale() const;
QVoice voice() const;
Q_INVOKABLE QList<QVoice> availableVoices() const;
double rate() const;
double pitch() const;
double volume() const;
Q_INVOKABLE static QStringList availableEngines();
template <typename Functor>
void synthesize(const QString &text,
#ifdef Q_QDOC
const QObject *receiver,
#else
const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
# endif // Q_QDOC
Functor &&func)
{
using Prototype2 = void(*)(QAudioFormat, QByteArray);
using Prototype1 = void(*)(QAudioBuffer);
if constexpr (qxp::is_detected_v<CompatibleCallbackTest2, Functor>) {
synthesizeImpl(text, QtPrivate::makeCallableObject<Prototype2>(std::forward<Functor>(func)),
receiver, SynthesizeOverload::AudioFormatByteArray);
} else if constexpr (qxp::is_detected_v<CompatibleCallbackTest1, Functor>) {
synthesizeImpl(text, QtPrivate::makeCallableObject<Prototype1>(std::forward<Functor>(func)),
receiver, SynthesizeOverload::AudioBuffer);
} else {
static_assert(QtPrivate::type_dependent_false<Functor>(),
"Incompatible functor signature, must be either "
"(QAudioFormat, QByteArray) or (QAudioBuffer)!");
}
}
// synthesize to a functor or function pointer (without context)
template <typename Functor>
void synthesize(const QString &text, Functor &&func)
{
synthesize(text, nullptr, std::forward<Functor>(func));
}
template <typename ...Args>
QList<QVoice> 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<std::is_same<q20::remove_cvref_t<Args>, QLocale>...>) {
locale = std::get<QLocale>(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<QVoice> allVoices(const QLocale *locale) const;
private:
template <typename Functor>
using CompatibleCallbackTest2 = decltype(QtPrivate::makeCallableObject<void(*)(QAudioFormat, QByteArray)>(std::declval<Functor>()));
template <typename Functor>
using CompatibleCallbackTest1 = decltype(QtPrivate::makeCallableObject<void(*)(QAudioBuffer)>(std::declval<Functor>()));
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 <typename T, typename Tuple> struct LastIndexOf;
template <typename T, typename ...Ts>
struct LastIndexOf<T, std::tuple<Ts...>> {
template <qsizetype... Is>
static constexpr qsizetype lastIndexOf(std::integer_sequence<qsizetype, Is...>) {
return std::max({(std::is_same_v<T, Ts> ? Is : -1)...});
}
static constexpr qsizetype value =
lastIndexOf(std::make_integer_sequence<qsizetype, sizeof...(Ts)>{});
};
template <typename Arg0, typename ...Args>
bool voiceMatches(const QVoice &voice, Arg0 &&arg0, Args &&...args) const {
using ArgType = q20::remove_cvref_t<Arg0>;
bool matches = [&]{
if constexpr (std::is_same_v<ArgType, QLocale>) {
return voice.locale() == arg0;
} else if constexpr (std::is_same_v<ArgType, QLocale::Language>) {
return voice.locale().language() == arg0;
} else if constexpr (std::is_same_v<ArgType, QLocale::Territory>) {
return voice.locale().territory() == arg0;
} else if constexpr (std::is_same_v<ArgType, QVoice::Gender>) {
return voice.gender() == arg0;
} else if constexpr (std::is_same_v<ArgType, QVoice::Age>) {
return voice.age() == arg0;
} else if constexpr (std::disjunction_v<std::is_convertible<ArgType, QString>,
std::is_convertible<ArgType, QStringView>>) {
return voice.name() == arg0;
} else if constexpr (std::is_same_v<ArgType, QRegularExpression>) {
return arg0.match(voice.name()).hasMatch();
} else {
static_assert(QtPrivate::type_dependent_false<Arg0>(),
"Type cannot be matched to a QVoice property!");
return false;
}
}();
if constexpr (sizeof...(args) > 0) {
static_assert(LastIndexOf<ArgType, std::tuple<q20::remove_cvref_t<Args>...>>::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