diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f152ac94a0..80b12de4357 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ find_package(Qt6 Sql Svg Test + TextToSpeech Widgets Xml OPTIONAL_COMPONENTS diff --git a/src/Audio/AudioOutput.cc b/src/Audio/AudioOutput.cc index 759c2152b96..36b2b993d73 100644 --- a/src/Audio/AudioOutput.cc +++ b/src/Audio/AudioOutput.cc @@ -14,7 +14,7 @@ #define MAX_TEXT_QUEUE_SIZE 20U -QGC_LOGGING_CATEGORY( AudioOutputLog, "qgc.audio.audiooutput" ); +QGC_LOGGING_CATEGORY(AudioOutputLog, "qgc.audio.audiooutput"); // qt.speech.tts.flite // qt.speech.tts.android @@ -38,36 +38,36 @@ const QHash AudioOutput::s_textHash = { { "PITOT", "pee toe" }, }; -Q_APPLICATION_STATIC( AudioOutput, s_audioOutput ); +Q_APPLICATION_STATIC(AudioOutput, s_audioOutput); AudioOutput* AudioOutput::instance() { return s_audioOutput(); } -AudioOutput::AudioOutput( QObject* parent ) - : QTextToSpeech( QStringLiteral("none"), parent ) +AudioOutput::AudioOutput(QObject* parent) + : QTextToSpeech(QStringLiteral("none"), parent) { - ( void ) connect( this, &QTextToSpeech::stateChanged, []( QTextToSpeech::State state ) { - qCDebug( AudioOutputLog ) << Q_FUNC_INFO << "State:" << state; + (void) connect(this, &QTextToSpeech::stateChanged, [](QTextToSpeech::State state) { + qCDebug(AudioOutputLog) << Q_FUNC_INFO << "State:" << state; }); - ( void ) connect( this, &QTextToSpeech::errorOccurred, []( QTextToSpeech::ErrorReason reason, const QString &errorString ) { - qCDebug( AudioOutputLog ) << Q_FUNC_INFO << "Error: (" << reason << ") " << errorString; + (void) connect(this, &QTextToSpeech::errorOccurred, [](QTextToSpeech::ErrorReason reason, const QString &errorString) { + qCDebug(AudioOutputLog) << Q_FUNC_INFO << "Error: (" << reason << ") " << errorString; }); - ( void ) connect( this, &QTextToSpeech::volumeChanged, []( double volume ) { - qCDebug( AudioOutputLog ) << Q_FUNC_INFO << "volume:" << volume; + (void) connect(this, &QTextToSpeech::volumeChanged, [](double volume) { + qCDebug(AudioOutputLog) << Q_FUNC_INFO << "volume:" << volume; }); - if ( !QTextToSpeech::availableEngines().isEmpty() ) { - if ( setEngine( QString() ) ) { // Autoselect engine by priority - qCDebug( AudioOutputLog ) << Q_FUNC_INFO << "engine:" << engine(); - if ( availableLocales().contains( QLocale( "en_US" ) ) ) { - setLocale( QLocale( "en_US" ) ); + if (!QTextToSpeech::availableEngines().isEmpty()) { + if (setEngine(QString())) { // Autoselect engine by priority + qCDebug(AudioOutputLog) << Q_FUNC_INFO << "engine:" << engine(); + if (availableLocales().contains(QLocale("en_US"))) { + setLocale(QLocale("en_US")); } - ( void ) connect( this, &AudioOutput::mutedChanged, [ this ]( bool muted ) { - qCDebug( AudioOutputLog ) << Q_FUNC_INFO << "muted:" << muted; - ( void ) QMetaObject::invokeMethod( this, "setVolume", Qt::AutoConnection, muted ? 0. : 1. ); + (void) connect(this, &AudioOutput::mutedChanged, [ this ](bool muted) { + qCDebug(AudioOutputLog) << Q_FUNC_INFO << "muted:" << muted; + (void) QMetaObject::invokeMethod(this, "setVolume", Qt::AutoConnection, muted ? 0. : 1.); }); } } @@ -78,52 +78,52 @@ bool AudioOutput::isMuted() const return m_muted; } -void AudioOutput::setMuted( bool enable ) +void AudioOutput::setMuted(bool enable) { - if ( enable != isMuted() ) { + if (enable != isMuted()) { m_muted = enable; - emit mutedChanged( m_muted ); + emit mutedChanged(m_muted); } } -void AudioOutput::read( const QString& text, AudioOutput::TextMods textMods ) +void AudioOutput::read(const QString& text, AudioOutput::TextMods textMods) { - if( m_muted ) { + if(m_muted) { return; } - if ( !engineCapabilities().testFlag( QTextToSpeech::Capability::Speak ) ) { - qCWarning( AudioOutputLog ) << Q_FUNC_INFO << "Speech Not Supported:" << text; + if (!engineCapabilities().testFlag(QTextToSpeech::Capability::Speak)) { + qCWarning(AudioOutputLog) << Q_FUNC_INFO << "Speech Not Supported:" << text; return; } - if ( m_textQueueSize > MAX_TEXT_QUEUE_SIZE ) { - ( void ) QMetaObject::invokeMethod( this, "stop", Qt::AutoConnection, QTextToSpeech::BoundaryHint::Default ); + if (m_textQueueSize > MAX_TEXT_QUEUE_SIZE) { + (void) QMetaObject::invokeMethod(this, "stop", Qt::AutoConnection, QTextToSpeech::BoundaryHint::Default); } - QString outText = AudioOutput::fixTextMessageForAudio( text ); + QString outText = AudioOutput::fixTextMessageForAudio(text); - if ( textMods.testFlag( AudioOutput::TextMod::Translate ) ) { - outText = QCoreApplication::translate("AudioOutput", outText.toStdString().c_str() ); + if (textMods.testFlag(AudioOutput::TextMod::Translate)) { + outText = QCoreApplication::translate("AudioOutput", outText.toStdString().c_str()); } qsizetype index = -1; - ( void ) QMetaObject::invokeMethod( this, "enqueue", Qt::AutoConnection, qReturnArg( m_textQueueSize ), outText ); - if ( index != -1 ) { + (void) QMetaObject::invokeMethod(this, "enqueue", Qt::AutoConnection, qReturnArg(m_textQueueSize), outText); + if (index != -1) { m_textQueueSize = index; } } -bool AudioOutput::getMillisecondString( const QString& string, QString& match, int& number ) +bool AudioOutput::getMillisecondString(const QString& string, QString& match, int& number) { - static const QRegularExpression regexp( "([0-9]+ms)" ); + static const QRegularExpression regexp("([0-9]+ms)"); bool result = false; - for ( const QRegularExpressionMatch &tempMatch : regexp.globalMatch( string ) ) { - if ( tempMatch.hasMatch() ) { - match = tempMatch.captured( 0 ); - number = tempMatch.captured( 0 ).replace( "ms", "" ).toInt(); + for (const QRegularExpressionMatch &tempMatch : regexp.globalMatch(string)) { + if (tempMatch.hasMatch()) { + match = tempMatch.captured(0); + number = tempMatch.captured(0).replace("ms", "").toInt(); result = true; break; } @@ -132,63 +132,63 @@ bool AudioOutput::getMillisecondString( const QString& string, QString& match, i return result; } -QString AudioOutput::fixTextMessageForAudio( const QString& string ) +QString AudioOutput::fixTextMessageForAudio(const QString& string) { QString result = string; - for ( const QString& word: string.split( ' ', Qt::SkipEmptyParts ) ) { - if ( s_textHash.contains( word.toUpper() ) ) { - result.replace( word, s_textHash.value( word.toUpper() ) ); + for (const QString& word: string.split(' ', Qt::SkipEmptyParts)) { + if (s_textHash.contains(word.toUpper())) { + result.replace(word, s_textHash.value(word.toUpper())); } } // Convert negative numbers - static const QRegularExpression negNumRegex( QStringLiteral( "(-)[0-9]*\\.?[0-9]" ) ); - QRegularExpressionMatch negNumRegexMatch = negNumRegex.match( result ); - while ( negNumRegexMatch.hasMatch() ) { - if ( !negNumRegexMatch.captured( 1 ).isNull() ) { - result.replace( negNumRegexMatch.capturedStart( 1 ), negNumRegexMatch.capturedEnd( 1 ) - negNumRegexMatch.capturedStart( 1 ), tr(" negative ")); + static const QRegularExpression negNumRegex(QStringLiteral("(-)[0-9]*\\.?[0-9]")); + QRegularExpressionMatch negNumRegexMatch = negNumRegex.match(result); + while (negNumRegexMatch.hasMatch()) { + if (!negNumRegexMatch.captured(1).isNull()) { + result.replace(negNumRegexMatch.capturedStart(1), negNumRegexMatch.capturedEnd(1) - negNumRegexMatch.capturedStart(1), tr(" negative ")); } - negNumRegexMatch = negNumRegex.match( result ); + negNumRegexMatch = negNumRegex.match(result); } // Convert real number with decimal point - static const QRegularExpression realNumRegex( QStringLiteral( "([0-9]+)(\\.)([0-9]+)" ) ); - QRegularExpressionMatch realNumRegexMatch = realNumRegex.match( result ); - while ( realNumRegexMatch.hasMatch() ) { - if ( !realNumRegexMatch.captured( 2 ).isNull() ) { - result.replace(realNumRegexMatch.capturedStart( 2 ), realNumRegexMatch.capturedEnd( 2 ) - realNumRegexMatch.capturedStart( 2 ), tr( " point " )); + static const QRegularExpression realNumRegex(QStringLiteral("([0-9]+)(\\.)([0-9]+)")); + QRegularExpressionMatch realNumRegexMatch = realNumRegex.match(result); + while (realNumRegexMatch.hasMatch()) { + if (!realNumRegexMatch.captured(2).isNull()) { + result.replace(realNumRegexMatch.capturedStart(2), realNumRegexMatch.capturedEnd(2) - realNumRegexMatch.capturedStart(2), tr(" point ")); } - realNumRegexMatch = realNumRegex.match( result ); + realNumRegexMatch = realNumRegex.match(result); } // Convert meter postfix after real number - static const QRegularExpression realNumMeterRegex( QStringLiteral( "[0-9]*\\.?[0-9]\\s?(m)([^A-Za-z]|$)" ) ); - QRegularExpressionMatch realNumMeterRegexMatch = realNumMeterRegex.match( result ); - while ( realNumMeterRegexMatch.hasMatch() ) { - if ( !realNumMeterRegexMatch.captured( 1 ).isNull( ) ) { - result.replace( realNumMeterRegexMatch.capturedStart( 1 ), realNumMeterRegexMatch.capturedEnd( 1 ) - realNumMeterRegexMatch.capturedStart( 1 ), tr(" meters")); + static const QRegularExpression realNumMeterRegex(QStringLiteral("[0-9]*\\.?[0-9]\\s?(m)([^A-Za-z]|$)")); + QRegularExpressionMatch realNumMeterRegexMatch = realNumMeterRegex.match(result); + while (realNumMeterRegexMatch.hasMatch()) { + if (!realNumMeterRegexMatch.captured(1).isNull()) { + result.replace(realNumMeterRegexMatch.capturedStart(1), realNumMeterRegexMatch.capturedEnd(1) - realNumMeterRegexMatch.capturedStart(1), tr(" meters")); } - realNumMeterRegexMatch = realNumMeterRegex.match( result ); + realNumMeterRegexMatch = realNumMeterRegex.match(result); } QString match; int number; - if ( getMillisecondString( string, match, number ) && ( number > 1000 ) ) { + if (getMillisecondString(string, match, number) && (number > 1000)) { QString newNumber; - if ( number < 60000 ) { + if (number < 60000) { const int seconds = number / 1000; - newNumber = QString( "%1 second%2" ).arg( seconds ).arg( seconds > 1 ? "s" : "" ); + newNumber = QString("%1 second%2").arg(seconds).arg(seconds > 1 ? "s" : ""); } else { const int minutes = number / 60000; - const int seconds = ( number - ( minutes * 60000 ) ) / 1000; - if ( !seconds ) { - newNumber = QString( "%1 minute%2" ).arg( minutes ).arg( minutes > 1 ? "s" : "" ); + const int seconds = (number - (minutes * 60000)) / 1000; + if (!seconds) { + newNumber = QString("%1 minute%2").arg(minutes).arg(minutes > 1 ? "s" : ""); } else { - newNumber = QString( "%1 minute%2 and %3 second%4" ).arg( minutes ).arg( minutes > 1 ? "s" : "" ).arg( seconds ).arg( seconds > 1 ? "s" : "" ); + newNumber = QString("%1 minute%2 and %3 second%4").arg(minutes).arg(minutes > 1 ? "s" : "").arg(seconds).arg(seconds > 1 ? "s" : ""); } } - result.replace( match, newNumber ); + result.replace(match, newNumber); } return result; diff --git a/src/Audio/AudioOutput.h b/src/Audio/AudioOutput.h index 45b0692116d..1b2b9218646 100644 --- a/src/Audio/AudioOutput.h +++ b/src/Audio/AudioOutput.h @@ -13,41 +13,41 @@ #include #include -Q_DECLARE_LOGGING_CATEGORY( AudioOutputLog ) +Q_DECLARE_LOGGING_CATEGORY(AudioOutputLog) class AudioOutput : public QTextToSpeech { Q_OBJECT QML_ELEMENT - QML_UNCREATABLE( "" ) + QML_UNCREATABLE("") - Q_PROPERTY( bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged ) + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) public: enum class TextMod { None = 0, Translate = 1 << 0, }; - Q_DECLARE_FLAGS( TextMods, TextMod ) - Q_FLAG( TextMod ) + Q_DECLARE_FLAGS(TextMods, TextMod) + Q_FLAG(TextMod) - explicit AudioOutput( QObject* parent = nullptr ); + explicit AudioOutput(QObject* parent = nullptr); bool isMuted() const; - void setMuted( bool enable ); + void setMuted(bool enable); - void read( const QString& text, AudioOutput::TextMods textMods = TextMod::None ); + void read(const QString& text, AudioOutput::TextMods textMods = TextMod::None); static AudioOutput* instance(); - static bool getMillisecondString( const QString& string, QString& match, int& number ); - static QString fixTextMessageForAudio( const QString& string ); + static bool getMillisecondString(const QString& string, QString& match, int& number); + static QString fixTextMessageForAudio(const QString& string); signals: - void mutedChanged( bool muted ); + void mutedChanged(bool muted); private: qsizetype m_textQueueSize = 0; bool m_muted = false; static const QHash s_textHash; }; -Q_DECLARE_OPERATORS_FOR_FLAGS( AudioOutput::TextMods ) +Q_DECLARE_OPERATORS_FOR_FLAGS(AudioOutput::TextMods) diff --git a/tools/setup/ubuntu.sh b/tools/setup/ubuntu.sh index 742ab7495b8..0033d97202e 100644 --- a/tools/setup/ubuntu.sh +++ b/tools/setup/ubuntu.sh @@ -26,4 +26,8 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get -y --quiet --no-install-recommends i libxcb-xinerama0 \ libxkbcommon-x11-0 \ libxcb-cursor0 \ - libdrm-dev + libdrm-dev \ + libspeechd2 \ + flite \ + speech-dispatcher \ + speech-dispatcher-flite