Skip to content

Commit

Permalink
Audio: Remove QTextToSpeech Inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
HTRamsey committed Sep 7, 2024
1 parent eb15982 commit a703ff2
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 85 deletions.
114 changes: 58 additions & 56 deletions src/Audio/AudioOutput.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
****************************************************************************/

#include "AudioOutput.h"
#include "Fact.h"
#include "QGCLoggingCategory.h"

#include <QtCore/QRegularExpression>
#include <QtCore/qapplicationstatic.h>

#define MAX_TEXT_QUEUE_SIZE 20U
#include <QtTextToSpeech/QTextToSpeech>

QGC_LOGGING_CATEGORY(AudioOutputLog, "qgc.audio.audiooutput");
// qt.speech.tts.flite
// qt.speech.tts.android

const QHash<QString, QString> AudioOutput::s_textHash = {
const QHash<QString, QString> AudioOutput::_textHash = {
{ "ERR", "error" },
{ "POSCTL", "Position Control" },
{ "ALTCTL", "Altitude Control" },
Expand All @@ -31,82 +31,85 @@ const QHash<QString, QString> AudioOutput::s_textHash = {
{ "WP", "waypoint" },
{ "CMD", "command" },
{ "COMPID", "component eye dee" },
{ "params", "parameters" },
{ "id", "I.D." },
{ "PARAMS", "parameters" },
{ "ID", "I.D." },
{ "ADSB", "A.D.S.B." },
{ "EKF", "E.K.F." },
{ "PREARM", "pre arm" },
{ "PITOT", "pee toe" },
{ "SERVOX_FUNCTION","Servo X Function" },
};

Q_APPLICATION_STATIC(AudioOutput, s_audioOutput);

AudioOutput* AudioOutput::instance()
{
return s_audioOutput();
}
Q_APPLICATION_STATIC(AudioOutput, _audioOutput);

AudioOutput::AudioOutput(QObject* parent)
: QTextToSpeech(QStringLiteral("none"), parent)
AudioOutput::AudioOutput(QObject *parent)
: QObject(parent)
, _engine(new QTextToSpeech(QStringLiteral("none"), this))
{
// qCDebug(AudioOutputLog) << Q_FUNC_INFO << this;

(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::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 (_engine->setEngine(QString())) {
// Autoselect engine by priority
qCDebug(AudioOutputLog) << Q_FUNC_INFO << "engine:" << _engine->engine();
if (_engine->availableLocales().contains(QLocale("en_US"))) {
_engine->setLocale(QLocale("en_US"));
}

(void) connect(this, &AudioOutput::mutedChanged, [ this ](bool muted) {
(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) QMetaObject::invokeMethod(_engine, "setVolume", Qt::AutoConnection, muted ? 0. : 1.);
});
}
}

#ifdef QT_DEBUG
(void) connect(_engine, &QTextToSpeech::stateChanged, [](QTextToSpeech::State state) {
qCDebug(AudioOutputLog) << Q_FUNC_INFO << "State:" << state;
});
(void) connect(_engine, &QTextToSpeech::errorOccurred, [](QTextToSpeech::ErrorReason reason, const QString &errorString) {
qCDebug(AudioOutputLog) << Q_FUNC_INFO << "Error: (" << reason << ") " << errorString;
});
(void) connect(_engine, &QTextToSpeech::volumeChanged, [](double volume) {
qCDebug(AudioOutputLog) << Q_FUNC_INFO << "volume:" << volume;
});
#endif
}

AudioOutput::~AudioOutput()
{
// qCDebug(AudioOutputLog) << Q_FUNC_INFO << this;
}

bool AudioOutput::isMuted() const
AudioOutput *AudioOutput::instance()
{
return m_muted;
return _audioOutput();
}

void AudioOutput::setMuted(bool enable)
void AudioOutput::init(Fact *mutedFact)
{
if (enable != isMuted()) {
m_muted = enable;
emit mutedChanged(m_muted);
}
Q_CHECK_PTR(mutedFact);

(void) connect(mutedFact, &Fact::valueChanged, this, [this](QVariant value) {
setMuted(value.toBool());
});

setMuted(mutedFact->rawValue().toBool());
}

void AudioOutput::read(const QString& text, AudioOutput::TextMods textMods)
void AudioOutput::say(const QString &text, AudioOutput::TextMods textMods)
{
if(m_muted) {
if (_muted) {
return;
}

if (!engineCapabilities().testFlag(QTextToSpeech::Capability::Speak)) {
if (!_engine->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 (_textQueueSize > kMaxTextQueueSize) {
(void) QMetaObject::invokeMethod(_engine, "stop", Qt::AutoConnection, QTextToSpeech::BoundaryHint::Default);
}

QString outText = AudioOutput::fixTextMessageForAudio(text);
Expand All @@ -116,13 +119,13 @@ void AudioOutput::read(const QString& text, AudioOutput::TextMods textMods)
}

qsizetype index = -1;
(void) QMetaObject::invokeMethod(this, "enqueue", Qt::AutoConnection, qReturnArg(index), outText);
(void) QMetaObject::invokeMethod(_engine, "enqueue", Qt::AutoConnection, qReturnArg(index), outText);
if (index != -1) {
m_textQueueSize = index;
_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)");

Expand All @@ -140,13 +143,13 @@ bool AudioOutput::getMillisecondString(const QString& string, QString& match, in
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 (_textHash.contains(word.toUpper())) {
result.replace(word, _textHash.value(word.toUpper()));
}
}

Expand All @@ -155,7 +158,7 @@ QString AudioOutput::fixTextMessageForAudio(const QString& string)
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 "));
result.replace(negNumRegexMatch.capturedStart(1), negNumRegexMatch.capturedEnd(1) - negNumRegexMatch.capturedStart(1), QStringLiteral(" negative "));
}
negNumRegexMatch = negNumRegex.match(result);
}
Expand All @@ -165,7 +168,7 @@ QString AudioOutput::fixTextMessageForAudio(const QString& string)
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 "));
result.replace(realNumRegexMatch.capturedStart(2), realNumRegexMatch.capturedEnd(2) - realNumRegexMatch.capturedStart(2), QStringLiteral(" point "));
}
realNumRegexMatch = realNumRegex.match(result);
}
Expand All @@ -175,7 +178,7 @@ QString AudioOutput::fixTextMessageForAudio(const QString& string)
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"));
result.replace(realNumMeterRegexMatch.capturedStart(1), realNumMeterRegexMatch.capturedEnd(1) - realNumMeterRegexMatch.capturedStart(1), QStringLiteral(" meters"));
}
realNumMeterRegexMatch = realNumMeterRegex.match(result);
}
Expand All @@ -186,14 +189,13 @@ QString AudioOutput::fixTextMessageForAudio(const QString& string)
QString newNumber;
if (number < 60000) {
const int seconds = number / 1000;
newNumber = QString("%1 second%2").arg(seconds).arg(seconds > 1 ? "s" : "");
newNumber = QStringLiteral("%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" : "");
} else {
newNumber = QString("%1 minute%2 and %3 second%4").arg(minutes).arg(minutes > 1 ? "s" : "").arg(seconds).arg(seconds > 1 ? "s" : "");
newNumber = QStringLiteral("%1 minute%2").arg(minutes).arg(minutes > 1 ? "s" : "");
if (seconds) {
(void) newNumber.append(QStringLiteral(" and %1 second%2").arg(seconds).arg(seconds > 1 ? "s" : ""));
}
}
result.replace(match, newNumber);
Expand Down
46 changes: 27 additions & 19 deletions src/Audio/AudioOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
#pragma once

#include <QtCore/QLoggingCategory>
#include <QtQmlIntegration/QtQmlIntegration>
#include <QtTextToSpeech/QTextToSpeech>
#include <QtCore/QObject>

class QTextToSpeech;
class Fact;

Q_DECLARE_LOGGING_CATEGORY(AudioOutputLog)

/// The AudioOutput class provides functionality for audio output using text-to-speech.
class AudioOutput : public QTextToSpeech
class AudioOutput : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")

Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)

Expand All @@ -30,54 +30,62 @@ class AudioOutput : public QTextToSpeech
None = 0,
Translate = 1 << 0,
};
Q_DECLARE_FLAGS(TextMods, TextMod)
Q_FLAG(TextMod)
Q_DECLARE_FLAGS(TextMods, TextMod)

/// Constructs an AudioOutput object.
/// @param parent The parent QObject.
explicit AudioOutput(QObject* parent = nullptr);
explicit AudioOutput(QObject *parent = nullptr);

/// Destructor for the AudioOutput class.
~AudioOutput();

/// Gets the singleton instance of AudioOutput.
/// @return The singleton instance.
static AudioOutput *instance();

/// Initialize the Singleton
void init(Fact *mutedFact);

/// Checks if the audio output is muted.
/// @return True if muted, false otherwise.
bool isMuted() const;
bool isMuted() const { return _muted; }

/// Sets the mute state of the audio output.
/// @param enable True to mute, false to unmute.
void setMuted(bool enable);
void setMuted(bool muted) { if (muted != _muted) { _muted = muted; emit mutedChanged(_muted); } }

/// Reads the specified text with optional text modifications.
/// @param text The text to be read.
/// @param textMods The text modifications to apply.
void read(const QString& text, AudioOutput::TextMods textMods = TextMod::None);

/// Gets the singleton instance of AudioOutput.
/// @return The singleton instance.
static AudioOutput* instance();
void say(const QString &text, AudioOutput::TextMods textMods = TextMod::None);

/// Extracts a millisecond value from the given string.
/// @param string The string to extract from.
/// @param match The extracted millisecond string.
/// @param number The extracted number.
/// @return True if extraction is successful, false otherwise.
static bool getMillisecondString(const QString& string, QString& match, int& number);
static bool getMillisecondString(const QString &string, QString &match, int &number);

/// Fixes text messages for audio output.
/// @param string The text message to fix.
/// @return The fixed text message.
static QString fixTextMessageForAudio(const QString& string);
static QString fixTextMessageForAudio(const QString &string);

signals:
/// Emitted when the mute state changes.
/// @param muted The new mute state.
void mutedChanged(bool muted);

private:
qsizetype m_textQueueSize = 0;
bool m_muted = false;
QTextToSpeech *_engine = nullptr;
qsizetype _textQueueSize = 0;
bool _muted = false;
Fact *_mutedFact = nullptr;

static const QHash<QString, QString> _textHash;

static constexpr qsizetype kMaxTextQueueSize = 20;

static const QHash<QString, QString> s_textHash;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(AudioOutput::TextMods)
6 changes: 3 additions & 3 deletions src/Audio/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
find_package(Qt6 REQUIRED COMPONENTS Core Qml TextToSpeech)
find_package(Qt6 REQUIRED COMPONENTS Core TextToSpeech)

qt_add_library(Audio STATIC
AudioOutput.cc
Expand All @@ -7,11 +7,11 @@ qt_add_library(Audio STATIC

target_link_libraries(Audio
PRIVATE
Qt6::TextToSpeech
FactSystem
Utilities
PUBLIC
Qt6::Core
Qt6::QmlIntegration
Qt6::TextToSpeech
)

target_include_directories(Audio PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
8 changes: 2 additions & 6 deletions src/QGCApplication.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <QtQml/QQmlContext>
#include <QtQml/QQmlApplicationEngine>

#include "Audio/AudioOutput.h"
#include "QGCConfig.h"
#include "QGCApplication.h"
#include "CmdLineOptParser.h"
Expand Down Expand Up @@ -416,12 +417,7 @@ void QGCApplication::_initForNormalAppBoot()
QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, QCoreApplication::quit, Qt::QueuedConnection);
_toolbox->corePlugin()->createRootWindow(_qmlAppEngine);

( void ) connect( _toolbox->settingsManager()->appSettings()->audioMuted(), &Fact::valueChanged, AudioOutput::instance(), []( QVariant value )
{
AudioOutput::instance()->setMuted( value.toBool() );
});
AudioOutput::instance()->setMuted( _toolbox->settingsManager()->appSettings()->audioMuted()->rawValue().toBool() );

AudioOutput::instance()->init(_toolbox->settingsManager()->appSettings()->audioMuted());
FollowMe::instance()->init();

// Image provider for PX4 Flow
Expand Down
2 changes: 1 addition & 1 deletion src/Vehicle/Vehicle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1928,7 +1928,7 @@ void Vehicle::virtualTabletJoystickValue(double roll, double pitch, double yaw,

void Vehicle::_say(const QString& text)
{
AudioOutput::instance()->read(text.toLower());
AudioOutput::instance()->say(text.toLower());
}

bool Vehicle::airship() const
Expand Down

0 comments on commit a703ff2

Please sign in to comment.