QGCAudioWorker.cpp 6.02 KB
Newer Older
1 2
#include <QSettings>
#include <QDebug>
3 4
#include <QCoreApplication>
#include <QFile>
5
#include <QRegularExpression>
6 7 8

#include "QGC.h"
#include "QGCAudioWorker.h"
9
#include "GAudioOutput.h"
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

#if defined Q_OS_MAC && defined QGC_SPEECH_ENABLED
#include <ApplicationServices/ApplicationServices.h>
#endif

// Speech synthesis is only supported with MSVC compiler
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED
// Documentation: http://msdn.microsoft.com/en-us/library/ee125082%28v=VS.85%29.aspx
#include <sapi.h>
#endif

#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED
// Using eSpeak for speech synthesis: following https://github.com/mondhs/espeak-sample/blob/master/sampleSpeak.cpp
#include <espeak/speak_lib.h>
#endif

#define QGC_GAUDIOOUTPUT_KEY QString("QGC_AUDIOOUTPUT_")

QGCAudioWorker::QGCAudioWorker(QObject *parent) :
    QObject(parent),
    voiceIndex(0),
31 32 33
    #if defined _MSC_VER && defined QGC_SPEECH_ENABLED
    pVoice(NULL),
    #endif
34 35 36 37 38
    #ifdef QGC_NOTIFY_TUNES_ENABLED
    sound(NULL),
    #endif
    emergency(false),
    muted(false)
39 40 41 42
{
    // Load settings
    QSettings settings;
    muted = settings.value(QGC_GAUDIOOUTPUT_KEY + "muted", muted).toBool();
43
}
44

45 46
void QGCAudioWorker::init()
{
47
#ifdef QGC_NOTIFY_TUNES_ENABLED
Don Gagne's avatar
Don Gagne committed
48
    sound = new QSound(":/res/Alert");
49
#endif
50 51

#if defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED
52
    espeak_Initialize(AUDIO_OUTPUT_SYNCH_PLAYBACK, 500, NULL, 0); // initialize for playback with 500ms buffer and no options (see speak_lib.h)
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
    espeak_VOICE *espeak_voice = espeak_GetCurrentVoice();
    espeak_voice->languages = "en-uk"; // Default to British English
    espeak_voice->identifier = NULL; // no specific voice file specified
    espeak_voice->name = "klatt"; // espeak voice name
    espeak_voice->gender = 2; // Female
    espeak_voice->age = 0; // age not specified
    espeak_SetVoiceByProperties(espeak_voice);
#endif

#if defined _MSC_VER && defined QGC_SPEECH_ENABLED

    if (FAILED(::CoInitialize(NULL)))
    {
        qDebug() << "ERROR: Creating COM object for audio output failed!";
    }
    else
    {

        HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);

        if (FAILED(hr))
        {
            qDebug() << "ERROR: Initializing voice for audio output failed!";
        }
    }

#endif
}

QGCAudioWorker::~QGCAudioWorker()
{
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED
85 86 87 88
	if (pVoice) {
		pVoice->Release();
		pVoice = NULL;
	}
89 90 91 92
    ::CoUninitialize();
#endif
}

93
void QGCAudioWorker::say(QString inText, int severity)
94
{
Don Gagne's avatar
Don Gagne committed
95 96 97 98 99 100
	static bool threadInit = false;
	if (!threadInit) {
		threadInit = true;
		init();
	}

101 102
    if (!muted)
    {
103
        QString text = _fixMillisecondString(inText);
104 105 106 107
        // Prepend high priority text with alert beep
        if (severity < GAudioOutput::AUDIO_SEVERITY_CRITICAL) {
            beep();
        }
108

109
#ifdef QGC_NOTIFY_TUNES_ENABLED
110 111 112 113
        // Wait for the last sound to finish
        while (!sound->isFinished()) {
            QGC::SLEEP::msleep(100);
        }
114
#endif
115

116
#if defined _MSC_VER && defined QGC_SPEECH_ENABLED
Don Gagne's avatar
Don Gagne committed
117 118 119 120
        HRESULT hr = pVoice->Speak(text.toStdWString().c_str(), SPF_DEFAULT, NULL);
		if (FAILED(hr)) {
			qDebug() << "Speak failed, HR:" << QString("%1").arg(hr, 0, 16);
		}
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
#elif defined Q_OS_LINUX && defined QGC_SPEECH_ENABLED
        // Set size of string for espeak: +1 for the null-character
        unsigned int espeak_size = strlen(text.toStdString().c_str()) + 1;
        espeak_Synth(text.toStdString().c_str(), espeak_size, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, NULL);

#elif defined Q_OS_MAC && defined QGC_SPEECH_ENABLED
        // Slashes necessary to have the right start to the sentence
        // copying data prevents SpeakString from reading additional chars
        text = "\\" + text;
        std::wstring str = text.toStdWString();
        unsigned char str2[1024] = {};
        memcpy(str2, text.toLatin1().data(), str.length());
        SpeakString(str2);

        // Block the thread while busy
        // because we run in our own thread, this doesn't
        // halt the main application
        while (SpeechBusy()) {
            QGC::SLEEP::msleep(100);
140
        }
141 142 143

#else
        // Make sure there isn't an unused variable warning when speech output is disabled
144
        Q_UNUSED(inText);
145
#endif
146 147 148 149 150 151 152 153 154 155 156 157 158 159
    }
}

void QGCAudioWorker::mute(bool mute)
{
    if (mute != muted)
    {
        this->muted = mute;
        QSettings settings;
        settings.setValue(QGC_GAUDIOOUTPUT_KEY + "muted", this->muted);
//        emit mutedChanged(muted);
    }
}

160 161 162 163 164
void QGCAudioWorker::beep()
{

    if (!muted)
    {
165
#ifdef QGC_NOTIFY_TUNES_ENABLED
Don Gagne's avatar
Don Gagne committed
166
        sound->play(":/res/Alert");
167
#endif
168 169 170
    }
}

171 172 173 174
bool QGCAudioWorker::isMuted()
{
    return this->muted;
}
175 176

bool QGCAudioWorker::_getMillisecondString(const QString& string, QString& match, int& number) {
dogmaphobic's avatar
dogmaphobic committed
177
    static QRegularExpression re("([0-9]+ms)");
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    QRegularExpressionMatchIterator i = re.globalMatch(string);
    while (i.hasNext()) {
        QRegularExpressionMatch qmatch = i.next();
        if (qmatch.hasMatch()) {
            match = qmatch.captured(0);
            number = qmatch.captured(0).replace("ms", "").toInt();
            return true;
        }
    }
    return false;
}

QString QGCAudioWorker::_fixMillisecondString(const QString& string) {
    QString match;
    QString newNumber;
    QString result = string;
    int number;
    if(_getMillisecondString(string, match, number) && number > 1000) {
        if(number < 60000) {
            int seconds = number / 1000;
            newNumber = QString("%1 second%2").arg(seconds).arg(seconds > 1 ? "s" : "");
        } else {
            int minutes = number / 60000;
            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" : "");
            }
        }
        result.replace(match, newNumber);
    }
    return result;
}