diff options
Diffstat (limited to 'src/plugins/tts')
31 files changed, 3245 insertions, 1 deletions
diff --git a/src/plugins/tts/common/qtexttospeechprocessor.cpp b/src/plugins/tts/common/qtexttospeechprocessor.cpp new file mode 100644 index 0000000..cbb7819 --- /dev/null +++ b/src/plugins/tts/common/qtexttospeechprocessor.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeechprocessor_p.h" + +#include <QtCore/QDateTime> + +QT_BEGIN_NAMESPACE + +QTextToSpeechProcessor::QTextToSpeechProcessor(): + m_stop(true), + m_idle(true), + m_paused(false), + m_rate(0), + m_pitch(0), + m_volume(100), + m_audio(0), + m_audioBuffer(0) +{ +} + +QTextToSpeechProcessor::~QTextToSpeechProcessor() +{ +} + +void QTextToSpeechProcessor::say(const QString &text, int voiceId) +{ + if (isInterruptionRequested()) + return; + QMutexLocker lock(&m_lock); + bool wasPaused = m_paused; + m_stop = true; // Cancel any previous utterance + m_idle = false; + m_paused = false; + m_nextText = text; + m_nextVoice = voiceId; + // If the speech was paused, one signal is needed to release the pause + // and another to start processing the new text. + m_speakSem.release(wasPaused ? 2 : 1); +} + +void QTextToSpeechProcessor::stop() +{ + QMutexLocker lock(&m_lock); + m_stop = true; + m_paused = false; + m_nextText.clear(); + m_speakSem.release(); +} + +void QTextToSpeechProcessor::pause() +{ + QMutexLocker lock(&m_lock); + m_paused = true; + m_speakSem.release(); +} + +void QTextToSpeechProcessor::resume() +{ + QMutexLocker lock(&m_lock); + m_paused = false; + m_speakSem.release(); +} + +bool QTextToSpeechProcessor::setRate(float rate) +{ + QMutexLocker lock(&m_lock); + if (rate >= -1.0 && rate <= 1.0) { + if (updateRate(rate)) { + m_rate = rate; + return true; + } + } + return false; +} + +bool QTextToSpeechProcessor::setPitch(float pitch) +{ + QMutexLocker lock(&m_lock); + if (pitch >= -1.0 && pitch <= 1.0) { + if (updatePitch(pitch)) { + m_pitch = pitch; + return true; + } + } + return false; +} + +bool QTextToSpeechProcessor::setVolume(int volume) +{ + QMutexLocker lock(&m_lock); + if (volume >= 0 && volume <= 100) { + if (updateVolume(volume)) { + m_volume = volume; + return true; + } + } + return false; +} + +bool QTextToSpeechProcessor::isIdle() const +{ + QMutexLocker lock(&m_lock); + return m_idle; +} + +float QTextToSpeechProcessor::rate() const +{ + QMutexLocker lock(&m_lock); + return m_rate; +} + +float QTextToSpeechProcessor::pitch() const +{ + QMutexLocker lock(&m_lock); + return m_pitch; +} + +int QTextToSpeechProcessor::volume() const +{ + QMutexLocker lock(&m_lock); + return m_volume; +} + +void QTextToSpeechProcessor::start(QThread::Priority priority) +{ + QThread::start(priority); +} + +void QTextToSpeechProcessor::exit(int retcode) +{ + QThread::exit(retcode); + QThread::requestInterruption(); + stop(); + if (!QThread::wait(5000)) { + QThread::terminate(); + QThread::wait(); + } +} + +void QTextToSpeechProcessor::run() +{ + int statusCode = 0; + forever { + m_lock.lock(); + if (!m_speakSem.tryAcquire()) { + m_idle = true; + m_lock.unlock(); + emit notSpeaking(statusCode); // Going idle + m_speakSem.acquire(); + m_lock.lock(); + } + if (isInterruptionRequested()) { + if (m_audio) { + delete m_audio; + m_audio = 0; + m_audioBuffer = 0; + } + m_lock.unlock(); + break; + } + m_stop = false; + if (!m_nextText.isEmpty()) { + QString text = m_nextText; + int voice = m_nextVoice; + m_nextText.clear(); + m_lock.unlock(); + statusCode = processText(text, voice); + } else { + m_lock.unlock(); + } + } +} + +bool QTextToSpeechProcessor::audioStart(int sampleRate, int channelCount, QString *errorString) +{ + QMutexLocker lock(&m_lock); + QAudioFormat format; + format.setSampleRate(sampleRate); + format.setChannelCount(channelCount); + format.setSampleSize(16); + format.setSampleType(QAudioFormat::SignedInt); + format.setCodec("audio/pcm"); + if (errorString) + *errorString = QString(); + if (m_audio) + delete m_audio; + m_audio = new QAudioOutput(format); + m_audioBuffer = m_audio->start(); + updateVolume(m_volume); + if (m_audioBuffer && m_audio->state() == QAudio::IdleState) + return true; + if (errorString) + *errorString = QLatin1String("Failed to start audio output (error ") + + QString::number(m_audio->error()) + QLatin1Char(')'); + delete m_audio; + m_audio = 0; + m_audioBuffer = 0; + return false; +} + +bool QTextToSpeechProcessor::audioOutput(const char *data, qint64 dataSize, QString *errorString) +{ + bool ret = true; + int bytesWritten = 0; + QString error; + forever { + m_lock.lock(); + if (m_paused) { + m_audio->suspend(); + do { + m_lock.unlock(); + m_speakSem.acquire(); // Wait for any command + m_lock.lock(); + } while (m_paused); + m_audio->resume(); + } + if (m_stop || !m_audioBuffer + || m_audio->state() == QAudio::StoppedState || isInterruptionRequested()) { + if (m_audio->error() != QAudio::NoError) { + error = QLatin1String("Audio error (") + + QString::number(m_audio->error()) + QLatin1Char(')'); + } + m_lock.unlock(); + ret = false; + break; + } + bytesWritten += m_audioBuffer->write(data + bytesWritten, dataSize - bytesWritten); + m_lock.unlock(); + if (bytesWritten >= dataSize) + break; + QThread::msleep(50); + } + if (errorString) + *errorString = error; + return ret; +} + +void QTextToSpeechProcessor::audioStop(bool abort) +{ + QMutexLocker lock(&m_lock); + if (m_audio) { + if (abort) { + m_audio->reset(); // Discard buffered audio + } else { + // TODO: Find a way to reliably check if all the audio has been written out before stopping + m_audioBuffer->write(QByteArray(1024, 0)); + QThread::msleep(200); + m_audio->stop(); + } + delete m_audio; + m_audio = 0; + m_audioBuffer = 0; + } +} + +bool QTextToSpeechProcessor::updateRate(float rate) +{ + Q_UNUSED(rate); + return true; +} + +bool QTextToSpeechProcessor::updatePitch(float pitch) +{ + Q_UNUSED(pitch); + return true; +} + +bool QTextToSpeechProcessor::updateVolume(int volume) +{ + if (m_audio) + m_audio->setVolume(((qreal)volume) / 100.0); + return true; +} + + +QT_END_NAMESPACE diff --git a/src/plugins/tts/common/qtexttospeechprocessor_p.h b/src/plugins/tts/common/qtexttospeechprocessor_p.h new file mode 100644 index 0000000..094fe73 --- /dev/null +++ b/src/plugins/tts/common/qtexttospeechprocessor_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHPROCESSOR_P_H +#define QTEXTTOSPEECHPROCESSOR_P_H + +#include "qvoice.h" + +#include <QtCore/QString> +#include <QtCore/QThread> +#include <QtCore/QMutex> +#include <QtCore/QSemaphore> +#include <QtCore/QIODevice> +#include <QtMultimedia/QAudioOutput> + +QT_BEGIN_NAMESPACE + +// A common base class for text-to-speech engine integrations +// that require audio output implementation and thread handling. +// +// QAudioOutput is used for audio, and each call to say() cancels +// any previous processing. The public interface is thread-safe. +class QTextToSpeechProcessor : public QThread { + Q_OBJECT + +public: + struct VoiceInfo + { + int id; + QString name; + QString locale; + QVoice::Gender gender; + QVoice::Age age; + }; + QTextToSpeechProcessor(); + ~QTextToSpeechProcessor(); + void say(const QString &text, int voiceId); + void stop(); + void pause(); + void resume(); + bool isIdle() const; + bool setRate(float rate); + bool setPitch(float pitch); + bool setVolume(int volume); + float rate() const; + float pitch() const; + int volume() const; + virtual const QVector<VoiceInfo> &voices() const = 0; + +protected: + // These are re-implemented QThread methods. + // exit() waits until the processor thread finishes or the wait times out. + void start(QThread::Priority = QThread::InheritPriority); + void exit(int retcode = 0); + + // These methods can be used for audio output. + // audioOutput() blocks until all the audio has been written or processing + // is interrupted. + bool audioStart(int sampleRate, int channelCount, QString *errorString = 0); + bool audioOutput(const char* data, qint64 dataSize, QString *errorString = 0); + void audioStop(bool abort = false); + + // These methods should be re-implemented if the parameters need + // to be changed while TTS is speaking. By default, updateVolume() just + // changes the QAudioOutput volume. The other methods do nothing by default. + virtual bool updateRate(float rate); + virtual bool updatePitch(float pitch); + virtual bool updateVolume(int volume); + + // This method is called from the internal processor thread, and should block + // until the given text has been processed or processing is interrupted. + virtual int processText(const QString &text, int voiceId) = 0; + +signals: + // This signal is emitted when the processor goes to idle state, i.e. when no + // new text is set to be spoken. The parameter is the latest return value of + // processText(). As the signal is emitted from the internal thread, the recipient + // should call isIdle() to get updated state. + void notSpeaking(int statusCode); + +private: + void run() Q_DECL_OVERRIDE; + mutable QMutex m_lock; + volatile bool m_stop; + volatile bool m_idle; + volatile bool m_paused; + float m_rate; + float m_pitch; + int m_volume; + QSemaphore m_speakSem; + QString m_nextText; + int m_nextVoice; + QAudioOutput *m_audio; + QIODevice *m_audioBuffer; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/flite/flite.pro b/src/plugins/tts/flite/flite.pro new file mode 100644 index 0000000..f80bea0 --- /dev/null +++ b/src/plugins/tts/flite/flite.pro @@ -0,0 +1,23 @@ +TARGET = qttexttospeech_flite +QT = core multimedia texttospeech + +PLUGIN_TYPE = texttospeech +PLUGIN_CLASS_NAME = QTextToSpeechEngineFlite +load(qt_plugin) + +HEADERS += \ + qtexttospeech_flite.h \ + qtexttospeech_flite_plugin.h \ + qtexttospeech_flite_processor.h \ + ../common/qtexttospeechprocessor_p.h + +SOURCES += \ + qtexttospeech_flite.cpp \ + qtexttospeech_flite_plugin.cpp \ + qtexttospeech_flite_processor.cpp \ + ../common/qtexttospeechprocessor.cpp + +OTHER_FILES += \ + flite_plugin.json + +LIBS += -lflite_cmu_us_kal16 -lflite_usenglish -lflite_cmulex -lflite diff --git a/src/plugins/tts/flite/flite_legal.qdoc b/src/plugins/tts/flite/flite_legal.qdoc new file mode 100644 index 0000000..f8f4d96 --- /dev/null +++ b/src/plugins/tts/flite/flite_legal.qdoc @@ -0,0 +1,217 @@ +/*! +\page legal-flite.html +\title Flite Library +\ingroup licensing + +\legalese +\code + +Flite is free software. + +We have kept the core code to BSD-like copyright, thus the system is +free to use in commercial products, with commercial extensions. GPL +code is only included as part of the build process and does not +taint any of the run-time code. + +As a collection it is distributed under the following license. Note +a few files in this distribution have a different but equally free +non-conflicting license, see below. + + Language Technologies Institute + Carnegie Mellon University + Copyright (c) 1999-2014 + All Rights Reserved. + + Permission is hereby granted, free of charge, to use and distribute + this software and its documentation without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of this work, and to + permit persons to whom this work is furnished to do so, subject to + the following conditions: + 1. The code must retain the above copyright notice, this list of + conditions and the following disclaimer. + 2. Any modifications must be clearly marked as such. + 3. Original authors' names are not deleted. + 4. The authors' names are not used to endorse or promote products + derived from this software without specific prior written + permission. + + CARNEGIE MELLON UNIVERSITY AND THE CONTRIBUTORS TO THIS WORK + DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT + SHALL CARNEGIE MELLON UNIVERSITY NOR THE CONTRIBUTORS BE LIABLE + FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + +All files within this distribution have the above license except +the following + +src/cg/cst_mlpg.h +src/cg/cst_mlpg.c +src/cg/cst_mlsa.h +src/cg/cst_mlsa.c +src/cg/cst_vc.h +src/cg/cst_vc.c +********************************************************************* +* * +* Nagoya Institute of Technology, Aichi, Japan, * +* Nara Institute of Science and Technology, Nara, Japan * +* and * +* Carnegie Mellon University, Pittsburgh, PA * +* Copyright (c) 2003-2004 * +* All Rights Reserved. * +* * +* Permission is hereby granted, free of charge, to use and * +* distribute this software and its documentation without * +* restriction, including without limitation the rights to use, * +* copy, modify, merge, publish, distribute, sublicense, and/or * +* sell copies of this work, and to permit persons to whom this * +* work is furnished to do so, subject to the following conditions: * +* * +* 1. The code must retain the above copyright notice, this list * +* of conditions and the following disclaimer. * +* 2. Any modifications must be clearly marked as such. * +* 3. Original authors' names are not deleted. * +* * +* NAGOYA INSTITUTE OF TECHNOLOGY, NARA INSTITUTE OF SCIENCE AND * +* TECHNOLOGY, CARNEGIE MELLON UNIVERSITY, AND THE CONTRIBUTORS TO * +* THIS WORK DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * +* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, * +* IN NO EVENT SHALL NAGOYA INSTITUTE OF TECHNOLOGY, NARA * +* INSTITUTE OF SCIENCE AND TECHNOLOGY, CARNEGIE MELLON UNIVERSITY, * +* NOR THE CONTRIBUTORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * +* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * +* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * +* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * +* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * +* * +********************************************************************* + +These functions are derived from the versions in festvox/src/vc/ as +modified by Tomoki Toda which in turn contain code derived from +NITECH's HTS system. Their copyright has the same freedoms as +as Flite's but under NAIST, NITECH and/or CMU. + +src/audio/au_wince.c +src/utils/cst_file_stdio.c +src/utils/cst_mmap_posix.c +src/utils/cst_mmap_win32.c +src/utils/cst_mmap_none.c +src/utils/cst_file_wince.c +sapi/ + are copyright Cepstral, LLC rather than CMU but fall under the same + free license as the above, except for the owner. (Note the SAPI stuff + probably doesn't work any more) + +doc/alice + Is the first two chapters of Alice in Wonderland as distributed by the + Gutenburg project and is now in the public domain + +src/regex/regexp.c +src/regex/regsub.c + + * Copyright (c) 1986 by University of Toronto. + * Written by Henry Spencer. Not derived from licensed software. + * + * Permission is granted to anyone to use this software for any + * purpose on any computer system, and to redistribute it freely, + * subject to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of + * this software, no matter how awful, even if they arise + * from defects in it. + * + * 2. The origin of this software must not be misrepresented, either + * by explicit claim or by omission. + * + * 3. Altered versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + +src/speech/rateconv.c + + * Copyright (c) 1992, 1995 by Markus Mummert + * + * Redistribution and use of this software, modifcation and inclusion + * into other forms of software are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of this software must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. If this software is redistributed in a modified condition + * it must reveal clearly that it has been modified. + +lang/usenglish/us_durz_cart.c +lang/usenglish/us_durz_cart.h +lang/usenglish/us_int_accent_cart.c +lang/usenglish/us_int_accent_cart.h +lang/usenglish/us_int_tone_cart.c +lang/usenglish/us_int_tone_cart.h +lang/usenglish/us_phoneset.c +lang/usenglish/us_f0lr.c + These are directly (or indirectly) compiled/derived from files that are + part of the Festival Speech Synthesis System (1.4.1). Hence they have + a joint copyright CMU/Edinburgh but with the same free license + +configure + # Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. + # + # This configure script is free software; the Free Software Foundation + # gives unlimited permission to copy, distribute and modify it. + +configure.sub +config.guess +missing +install-sh +mkinstalldirs + Copyright FSF, and under the GPL, these files are only used for + convenient configuration and are not part of the generated binary, + and therefore do not impose any GPL restrctions on the rest of the + system. But as they are standard methods for configuration they + are included. + +src/speech/g72x.h +src/speech/g721.c +src/speech/g72x.c +src/speech/g723_24.c +src/speech/g723_40.c + + * + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + * + +lang/cmu_grapheme_lex/grapheme_unitran_tables.c + * Copyright 2008-2012, University of Illinois at Urbana-Champaign * + * distributed under the Apache License, Version (2.0) * + * http://www.apache.org/licenses/LICENSE-2.0 * + * Original table developed by Richard Sproat and Kyoung-young Kim * + * Ported for Festvox by Gopala Anumachipalli gopalakr@cs.cmu.edu Sep 2012 * + * Then converted to C for CMU Flite (cmuflite.org) * + +\endcode +\endlegalese +*/ diff --git a/src/plugins/tts/flite/flite_plugin.json b/src/plugins/tts/flite/flite_plugin.json new file mode 100644 index 0000000..dad4918 --- /dev/null +++ b/src/plugins/tts/flite/flite_plugin.json @@ -0,0 +1,6 @@ +{ + "Keys": ["flite"], + "Provider": "flite", + "Version": 100, + "Features": [] +} diff --git a/src/plugins/tts/flite/qtexttospeech_flite.cpp b/src/plugins/tts/flite/qtexttospeech_flite.cpp new file mode 100755 index 0000000..364727f --- /dev/null +++ b/src/plugins/tts/flite/qtexttospeech_flite.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_flite.h" + +QT_BEGIN_NAMESPACE + +QTextToSpeechEngineFlite::QTextToSpeechEngineFlite( + const QVariantMap ¶meters, QObject *parent) : + QTextToSpeechEngine(parent), + m_state(QTextToSpeech::Ready), + m_processor(QTextToSpeechProcessorFlite::instance()) +{ + Q_UNUSED(parameters); +} + +QTextToSpeechEngineFlite::~QTextToSpeechEngineFlite() +{ +} + +QVector<QLocale> QTextToSpeechEngineFlite::availableLocales() const +{ + return m_locales; +} + +QVector<QVoice> QTextToSpeechEngineFlite::availableVoices() const +{ + return m_voices.values(m_currentLocale.name()).toVector(); +} + +void QTextToSpeechEngineFlite::say(const QString &text) +{ + int id = QTextToSpeechEngine::voiceData(m_currentVoice).toInt(); + m_state = QTextToSpeech::Speaking; + emit stateChanged(m_state); + m_processor->say(text, id); +} + +void QTextToSpeechEngineFlite::stop() +{ + m_processor->stop(); + m_state = QTextToSpeech::Ready; + emit stateChanged(m_state); +} + +void QTextToSpeechEngineFlite::pause() +{ + if (m_state == QTextToSpeech::Speaking) { + m_processor->pause(); + m_state = QTextToSpeech::Paused; + emit stateChanged(m_state); + } +} + +void QTextToSpeechEngineFlite::resume() +{ + if (m_state == QTextToSpeech::Paused) { + m_processor->resume(); + m_state = QTextToSpeech::Speaking; + emit stateChanged(m_state); + } +} + +double QTextToSpeechEngineFlite::rate() const +{ + return m_processor->rate(); +} + +bool QTextToSpeechEngineFlite::setRate(double rate) +{ + return m_processor->setRate(rate); +} + +double QTextToSpeechEngineFlite::pitch() const +{ + return m_processor->pitch(); +} + +bool QTextToSpeechEngineFlite::setPitch(double pitch) +{ + return m_processor->setPitch(pitch); +} + +QLocale QTextToSpeechEngineFlite::locale() const +{ + return m_currentLocale; +} + +bool QTextToSpeechEngineFlite::setLocale(const QLocale &locale) +{ + bool localeFound = false; + foreach (const QLocale &l, m_locales) { + if (l.name() == locale.name()) { + localeFound = true; + break; + } + } + if (!localeFound) + return false; + if (m_currentLocale.name() != locale.name()) { + m_currentLocale = locale; + m_currentVoice = availableVoices().at(0); + } + return true; +} + +int QTextToSpeechEngineFlite::volume() const +{ + return m_processor->volume(); +} + +bool QTextToSpeechEngineFlite::setVolume(int volume) +{ + return m_processor->setVolume(volume); +} + +QVoice QTextToSpeechEngineFlite::voice() const +{ + return m_currentVoice; +} + +bool QTextToSpeechEngineFlite::setVoice(const QVoice &voice) +{ + foreach (const QVoice &availableVoice, availableVoices()) { + if (QTextToSpeechEngine::voiceData(availableVoice) == QTextToSpeechEngine::voiceData(voice)) { + m_currentVoice = voice; + return true; + } + } + return false; +} + +QTextToSpeech::State QTextToSpeechEngineFlite::state() const +{ + return m_state; +} + +bool QTextToSpeechEngineFlite::init(QString *errorString) +{ + int i = 0; + const QVector<QTextToSpeechProcessor::VoiceInfo> &voices = m_processor->voices(); + foreach (const QTextToSpeechProcessor::VoiceInfo &voiceInfo, voices) { + QString name = voiceInfo.name; + QLocale locale(voiceInfo.locale); + QVoice voice = QTextToSpeechEngine::createVoice(name, voiceInfo.gender, voiceInfo.age, + QVariant(voiceInfo.id)); + m_voices.insert(voiceInfo.locale, voice); + if (!m_locales.contains(locale)) + m_locales.append(locale); + // Use the first available locale/voice as a fallback + if (i == 0) { + m_currentVoice = voice; + m_currentLocale = locale; + } + i++; + } + // Attempt to switch to the system locale + setLocale(QLocale::system()); + connect(m_processor.data(), &QTextToSpeechProcessor::notSpeaking, + this, &QTextToSpeechEngineFlite::onNotSpeaking); + if (errorString) + *errorString = QString(); + return true; +} + +void QTextToSpeechEngineFlite::onNotSpeaking(int statusCode) +{ + Q_UNUSED(statusCode); + if (m_state != QTextToSpeech::Ready && m_processor->isIdle()) { + m_state = QTextToSpeech::Ready; + emit stateChanged(m_state); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/tts/flite/qtexttospeech_flite.h b/src/plugins/tts/flite/qtexttospeech_flite.h new file mode 100755 index 0000000..311ebbd --- /dev/null +++ b/src/plugins/tts/flite/qtexttospeech_flite.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHENGINE_FLITE_H +#define QTEXTTOSPEECHENGINE_FLITE_H + +#include "qtexttospeech_flite_processor.h" +#include "qtexttospeechengine.h" +#include "qvoice.h" + +#include <QtCore/QString> +#include <QtCore/QLocale> +#include <QtCore/QVector> +#include <QtCore/QSharedPointer> + +#include <flite/flite.h> + +QT_BEGIN_NAMESPACE + +class QTextToSpeechEngineFlite : public QTextToSpeechEngine +{ + Q_OBJECT + +public: + QTextToSpeechEngineFlite(const QVariantMap ¶meters, QObject *parent); + virtual ~QTextToSpeechEngineFlite(); + + // Plug-in API: + QVector<QLocale> availableLocales() const Q_DECL_OVERRIDE; + QVector<QVoice> availableVoices() const Q_DECL_OVERRIDE; + void say(const QString &text) Q_DECL_OVERRIDE; + void stop() Q_DECL_OVERRIDE; + void pause() Q_DECL_OVERRIDE; + void resume() Q_DECL_OVERRIDE; + double rate() const Q_DECL_OVERRIDE; + bool setRate(double rate) Q_DECL_OVERRIDE; + double pitch() const Q_DECL_OVERRIDE; + bool setPitch(double pitch) Q_DECL_OVERRIDE; + QLocale locale() const Q_DECL_OVERRIDE; + bool setLocale(const QLocale &locale) Q_DECL_OVERRIDE; + int volume() const Q_DECL_OVERRIDE; + bool setVolume(int volume) Q_DECL_OVERRIDE; + QVoice voice() const Q_DECL_OVERRIDE; + bool setVoice(const QVoice &voice) Q_DECL_OVERRIDE; + QTextToSpeech::State state() const Q_DECL_OVERRIDE; + + // Internal API: + bool init(QString *errorString); + +public slots: + void onNotSpeaking(int statusCode); + +private: + QTextToSpeech::State m_state; + QSharedPointer<QTextToSpeechProcessorFlite> m_processor; + QLocale m_currentLocale; + QVector<QLocale> m_locales; + QVoice m_currentVoice; + // Voices mapped by their locale name. + QMultiMap<QString, QVoice> m_voices; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/flite/qtexttospeech_flite_plugin.cpp b/src/plugins/tts/flite/qtexttospeech_flite_plugin.cpp new file mode 100755 index 0000000..adf3f54 --- /dev/null +++ b/src/plugins/tts/flite/qtexttospeech_flite_plugin.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_flite_plugin.h" +#include "qtexttospeech_flite.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcSpeechTtsFlite, "qt.speech.tts.flite") + +QTextToSpeechEngine *QTextToSpeechPluginFlite::createTextToSpeechEngine( + const QVariantMap ¶meters, QObject *parent, QString *errorString) const +{ + QTextToSpeechEngineFlite *flite = new QTextToSpeechEngineFlite(parameters, parent); + if (flite && flite->init(errorString)) { + return flite; + } + delete flite; + return 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tts/flite/qtexttospeech_flite_plugin.h b/src/plugins/tts/flite/qtexttospeech_flite_plugin.h new file mode 100755 index 0000000..fe9ed64 --- /dev/null +++ b/src/plugins/tts/flite/qtexttospeech_flite_plugin.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHPLUGIN_FLITE_H +#define QTEXTTOSPEECHPLUGIN_FLITE_H + +#include "qtexttospeechplugin.h" +#include "qtexttospeechengine.h" + +#include <QtCore/QObject> +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcSpeechTtsFlite) + +class QTextToSpeechPluginFlite : public QObject, public QTextToSpeechPlugin +{ + Q_OBJECT + Q_INTERFACES(QTextToSpeechPlugin) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.speech.tts.plugin/5.0" + FILE "flite_plugin.json") + +public: + QTextToSpeechEngine *createTextToSpeechEngine( + const QVariantMap ¶meters, + QObject *parent, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/flite/qtexttospeech_flite_processor.cpp b/src/plugins/tts/flite/qtexttospeech_flite_processor.cpp new file mode 100644 index 0000000..5cb051d --- /dev/null +++ b/src/plugins/tts/flite/qtexttospeech_flite_processor.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_flite_processor.h" +#include "qtexttospeech_flite_plugin.h" + +#include <QtCore/QString> +#include <QtCore/QLocale> +#include <QtCore/QMap> + +#include <flite/flite.h> + +// en_US voice: +extern "C" cst_voice *register_cmu_us_kal16(); +extern "C" void unregister_cmu_us_kal16(cst_voice *vox); + +QT_BEGIN_NAMESPACE + +QWeakPointer<QTextToSpeechProcessorFlite> QTextToSpeechProcessorFlite::m_instance; +QMutex QTextToSpeechProcessorFlite::m_instanceLock; + +QSharedPointer<QTextToSpeechProcessorFlite> QTextToSpeechProcessorFlite::instance() +{ + QSharedPointer<QTextToSpeechProcessorFlite> inst = m_instance.toStrongRef(); + if (inst.isNull()) { + QMutexLocker lock(&m_instanceLock); + inst = m_instance.toStrongRef(); + if (inst.isNull()) { + inst = QSharedPointer<QTextToSpeechProcessorFlite>(new QTextToSpeechProcessorFlite()); + m_instance = inst; + } + } + Q_ASSERT(inst); + Q_ASSERT(inst == m_instance); + return inst; +} + +QTextToSpeechProcessorFlite::QTextToSpeechProcessorFlite(): + m_initialized(false), + m_currentVoice(-1) +{ + if (init()) { + m_initialized = true; + start(); + } else { + deinit(); + } +} + +QTextToSpeechProcessorFlite::~QTextToSpeechProcessorFlite() +{ + if (m_initialized) { + exit(); + deinit(); + } +} + +const QVector<QTextToSpeechProcessor::VoiceInfo> &QTextToSpeechProcessorFlite::voices() const +{ + return m_voices; +} + +int QTextToSpeechProcessorFlite::fliteOutputCb(const cst_wave *w, int start, int size, + int last, cst_audio_streaming_info *asi) +{ + QTextToSpeechProcessorFlite *processor = static_cast<QTextToSpeechProcessorFlite *>(asi->userdata); + if (processor) + return processor->fliteOutput(w, start, size, last, asi); + return CST_AUDIO_STREAM_STOP; +} + +int QTextToSpeechProcessorFlite::fliteOutput(const cst_wave *w, int start, int size, + int last, cst_audio_streaming_info *asi) +{ + Q_UNUSED(asi); + QString errorString; + if (start == 0) { + if (!audioStart(w->sample_rate, w->num_channels, &errorString)) { + if (!errorString.isEmpty()) + qCCritical(lcSpeechTtsFlite) << errorString; + return CST_AUDIO_STREAM_STOP; + } + } + int bytesToWrite = size * sizeof(short); + if (!audioOutput((const char *)(&w->samples[start]), bytesToWrite, &errorString)) { + if (!errorString.isEmpty()) + qCCritical(lcSpeechTtsFlite) << errorString; + audioStop(true); // Abort audio output + return CST_AUDIO_STREAM_STOP; + } + if (last == 1) + audioStop(); + return CST_AUDIO_STREAM_CONT; +} + +int QTextToSpeechProcessorFlite::processText(const QString &text, int voiceId) +{ + qCDebug(lcSpeechTtsFlite) << "processText() begin"; + if (voiceId >= 0 && voiceId < m_fliteVoices.size()) { + const FliteVoice &voiceInfo = m_fliteVoices.at(voiceId); + cst_voice *voice = voiceInfo.vox; + cst_audio_streaming_info *asi = new_audio_streaming_info(); + asi->asc = QTextToSpeechProcessorFlite::fliteOutputCb; + asi->userdata = (void *)this; + feat_set(voice->features, "streaming_info", audio_streaming_info_val(asi)); + setRateForVoice(voice, rate()); + setPitchForVoice(voice, pitch()); + flite_text_to_speech(text.toUtf8().constData(), voice, "none"); + } + qCDebug(lcSpeechTtsFlite) << "processText() end"; + return 0; +} + +void QTextToSpeechProcessorFlite::setRateForVoice(cst_voice *voice, float rate) +{ + float stretch = 1.0; + Q_ASSERT(rate >= -1.0 && rate <= 1.0); + // Stretch multipliers taken from Speech Dispatcher + if (rate < 0) + stretch -= rate * 2; + if (rate > 0) + stretch -= rate * (100.0 / 175.0); + feat_set_float(voice->features, "duration_stretch", stretch); +} + +void QTextToSpeechProcessorFlite::setPitchForVoice(cst_voice *voice, float pitch) +{ + float f0; + Q_ASSERT(pitch >= -1.0 && pitch <= 1.0); + // Conversion taken from Speech Dispatcher + f0 = (pitch * 80) + 100; + feat_set_float(voice->features, "int_f0_target_mean", f0); +} + +bool QTextToSpeechProcessorFlite::init() +{ + flite_init(); + FliteVoice voice_enus = { + register_cmu_us_kal16(), + unregister_cmu_us_kal16, + "kal16", + QLocale(QLocale::English, QLocale::UnitedStates).name(), + QVoice::Male, + QVoice::Adult + }; + m_fliteVoices.append(voice_enus); + + int totalVoiceCount = 0; + foreach (const FliteVoice &voice, m_fliteVoices) { + QTextToSpeechProcessor::VoiceInfo voiceInfo; + voiceInfo.name = voice.name; + voiceInfo.locale = voice.locale; + voiceInfo.age = voice.age; + voiceInfo.gender = voice.gender; + voiceInfo.id = totalVoiceCount; + m_voices.append(voiceInfo); + totalVoiceCount++; + } + return true; +} + +void QTextToSpeechProcessorFlite::deinit() +{ + foreach (const FliteVoice &voice, m_fliteVoices) + voice.unregister_func(voice.vox); + m_fliteVoices.clear(); + m_voices.clear(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/tts/flite/qtexttospeech_flite_processor.h b/src/plugins/tts/flite/qtexttospeech_flite_processor.h new file mode 100644 index 0000000..6eb1263 --- /dev/null +++ b/src/plugins/tts/flite/qtexttospeech_flite_processor.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHPROCESSOR_FLITE_H +#define QTEXTTOSPEECHPROCESSOR_FLITE_H + +#include "../common/qtexttospeechprocessor_p.h" + +#include "qtexttospeechengine.h" +#include "qvoice.h" + +#include <QtCore/QString> +#include <QtCore/QVector> +#include <QtCore/QSharedPointer> +#include <QtCore/QMutex> + +#include <flite/flite.h> + +QT_BEGIN_NAMESPACE + +// This is a reference counted singleton class. +// The instance is automatically deleted when no users remain. +class QTextToSpeechProcessorFlite : public QTextToSpeechProcessor { + Q_OBJECT + +public: + static QSharedPointer<QTextToSpeechProcessorFlite> instance(); + ~QTextToSpeechProcessorFlite(); + const QVector<VoiceInfo> &voices() const Q_DECL_OVERRIDE; + +private: + QTextToSpeechProcessorFlite(); + static int fliteOutputCb(const cst_wave *w, int start, int size, + int last, cst_audio_streaming_info *asi); + int fliteOutput(const cst_wave *w, int start, int size, + int last, cst_audio_streaming_info *asi); + int processText(const QString &text, int voiceId) Q_DECL_OVERRIDE; + void setRateForVoice(cst_voice *voice, float rate); + void setPitchForVoice(cst_voice *voice, float pitch); + bool init(); + void deinit(); + +private: + struct FliteVoice { + cst_voice *vox; + void (*unregister_func)(cst_voice *vox); + QString name; + QString locale; + QVoice::Gender gender; + QVoice::Age age; + }; + static QWeakPointer<QTextToSpeechProcessorFlite> m_instance; + static QMutex m_instanceLock; + bool m_initialized; + QVector<VoiceInfo> m_voices; + QVector<FliteVoice> m_fliteVoices; + int m_currentVoice; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/osx/osx.pro b/src/plugins/tts/osx/osx.pro new file mode 100644 index 0000000..4eb42e8 --- /dev/null +++ b/src/plugins/tts/osx/osx.pro @@ -0,0 +1,25 @@ +TARGET = qtexttospeech_speechosx +PLUGIN_TYPE = texttospeech +PLUGIN_CLASS_NAME = QTextToSpeechPluginOsx + +load(qt_plugin) + +QT += core texttospeech + +LIBS += -framework Cocoa + +HEADERS += \ + qtexttospeech_osx_plugin.h \ + qtexttospeech_osx.h \ + +OBJECTIVE_HEADERS += \ + qtexttospeech_osx.h \ + +SOURCES += \ + qtexttospeech_osx_plugin.cpp \ + +OBJECTIVE_SOURCES += \ + qtexttospeech_osx.mm \ + +OTHER_FILES += \ + osx_plugin.json diff --git a/src/plugins/tts/osx/osx_plugin.json b/src/plugins/tts/osx/osx_plugin.json new file mode 100644 index 0000000..5b039f3 --- /dev/null +++ b/src/plugins/tts/osx/osx_plugin.json @@ -0,0 +1,6 @@ +{ + "Keys": ["osx"], + "Provider": "osx", + "Version": 100, + "Features": [] +} diff --git a/src/plugins/tts/osx/qtexttospeech_osx.h b/src/plugins/tts/osx/qtexttospeech_osx.h new file mode 100644 index 0000000..4739326 --- /dev/null +++ b/src/plugins/tts/osx/qtexttospeech_osx.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHENGINE_OSX_H +#define QTEXTTOSPEECHENGINE_OSX_H + +#include <QtCore/qobject.h> +#include <QtCore/qvector.h> +#include <QtCore/qstring.h> +#include <QtCore/qlocale.h> +#include <QtTextToSpeech/qtexttospeechengine.h> +#include <QtTextToSpeech/qvoice.h> + +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(StateDelegate)); +Q_FORWARD_DECLARE_OBJC_CLASS(NSSpeechSynthesizer); +Q_FORWARD_DECLARE_OBJC_CLASS(NSString); + +QT_BEGIN_NAMESPACE + +class QTextToSpeechEngineOsx : public QTextToSpeechEngine +{ + Q_OBJECT + +public: + QTextToSpeechEngineOsx(const QVariantMap ¶meters, QObject *parent); + ~QTextToSpeechEngineOsx(); + + // Plug-in API: + QVector<QLocale> availableLocales() const Q_DECL_OVERRIDE; + QVector<QVoice> availableVoices() const Q_DECL_OVERRIDE; + void say(const QString &text) Q_DECL_OVERRIDE; + void stop() Q_DECL_OVERRIDE; + void pause() Q_DECL_OVERRIDE; + void resume() Q_DECL_OVERRIDE; + double rate() const Q_DECL_OVERRIDE; + bool setRate(double rate) Q_DECL_OVERRIDE; + double pitch() const Q_DECL_OVERRIDE; + bool setPitch(double pitch) Q_DECL_OVERRIDE; + QLocale locale() const Q_DECL_OVERRIDE; + bool setLocale(const QLocale &locale) Q_DECL_OVERRIDE; + int volume() const Q_DECL_OVERRIDE; + bool setVolume(int volume) Q_DECL_OVERRIDE; + QVoice voice() const Q_DECL_OVERRIDE; + bool setVoice(const QVoice &voice) Q_DECL_OVERRIDE; + QTextToSpeech::State state() const Q_DECL_OVERRIDE; + + void speechStopped(bool); + +private: + void updateVoices(); + bool isSpeaking() const; + bool isPaused() const; + + QTextToSpeech::State m_state; +// QVoice m_currentVoice; + + QVoice voiceForNSVoice(NSString *voiceString) const; + NSSpeechSynthesizer *speechSynthesizer; + QT_MANGLE_NAMESPACE(StateDelegate) *stateDelegate; + QVector<QLocale> m_locales; + QMultiMap<QString, QVoice> m_voices; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/osx/qtexttospeech_osx.mm b/src/plugins/tts/osx/qtexttospeech_osx.mm new file mode 100644 index 0000000..9092144 --- /dev/null +++ b/src/plugins/tts/osx/qtexttospeech_osx.mm @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <Cocoa/Cocoa.h> +#include "qtexttospeech_osx.h" +#include <qdebug.h> + +@interface QT_MANGLE_NAMESPACE(StateDelegate) : NSObject <NSSpeechSynthesizerDelegate> +{ + QT_PREPEND_NAMESPACE(QTextToSpeechEngineOsx) *speechPrivate; +} +@end + +@implementation QT_MANGLE_NAMESPACE(StateDelegate) +- (id)initWithSpeechPrivate:(QTextToSpeechEngineOsx *) priv { + self = [super init]; + speechPrivate = priv; + return self; +} +- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success { + Q_UNUSED(sender); + speechPrivate->speechStopped(success); +} +@end + +QT_BEGIN_NAMESPACE + +QTextToSpeechEngineOsx::QTextToSpeechEngineOsx(const QVariantMap &/*parameters*/, QObject *parent) + : QTextToSpeechEngine(parent), m_state(QTextToSpeech::Ready) +{ + stateDelegate = [[QT_MANGLE_NAMESPACE(StateDelegate) alloc] initWithSpeechPrivate:this]; + + speechSynthesizer = [[NSSpeechSynthesizer alloc] init]; + [speechSynthesizer setDelegate: stateDelegate]; + updateVoices(); +} + +QTextToSpeechEngineOsx::~QTextToSpeechEngineOsx() +{ + [speechSynthesizer release]; + [stateDelegate release]; +} + + +QTextToSpeech::State QTextToSpeechEngineOsx::state() const +{ + return m_state; +} + +bool QTextToSpeechEngineOsx::isSpeaking() const +{ + return [speechSynthesizer isSpeaking]; +} + +void QTextToSpeechEngineOsx::speechStopped(bool success) +{ + Q_UNUSED(success); + if (m_state != QTextToSpeech::Ready) { + m_state = QTextToSpeech::Ready; + emit stateChanged(m_state); + } +} + +void QTextToSpeechEngineOsx::say(const QString &text) +{ + if (text.isEmpty()) + return; + + if (m_state != QTextToSpeech::Ready) + stop(); + + if([speechSynthesizer isSpeaking]) { + [speechSynthesizer stopSpeaking]; + } + + NSString *ntext = text.toNSString(); + [speechSynthesizer startSpeakingString:ntext]; + + if (m_state != QTextToSpeech::Speaking) { + m_state = QTextToSpeech::Speaking; + emit stateChanged(m_state); + } +} + +void QTextToSpeechEngineOsx::stop() +{ + if([speechSynthesizer isSpeaking]) + [speechSynthesizer stopSpeaking]; +} + +void QTextToSpeechEngineOsx::pause() +{ + if ([speechSynthesizer isSpeaking]) { + [speechSynthesizer pauseSpeakingAtBoundary: NSSpeechWordBoundary]; + m_state = QTextToSpeech::Paused; + emit stateChanged(m_state); + } +} + +bool QTextToSpeechEngineOsx::isPaused() const +{ + return m_state == QTextToSpeech::Paused; +} + +void QTextToSpeechEngineOsx::resume() +{ + m_state = QTextToSpeech::Speaking; + emit stateChanged(m_state); + [speechSynthesizer continueSpeaking]; +} + +double QTextToSpeechEngineOsx::rate() const +{ + return ([speechSynthesizer rate] - 200) / 200.0; +} + +bool QTextToSpeechEngineOsx::setPitch(double pitch) +{ + // 30 to 65 + double p = 30.0 + ((pitch + 1.0) / 2.0) * 35.0; + [speechSynthesizer setObject:[NSNumber numberWithFloat:p] forProperty:NSSpeechPitchBaseProperty error:nil]; + return true; +} + +double QTextToSpeechEngineOsx::pitch() const +{ + double pitch = [[speechSynthesizer objectForProperty:NSSpeechPitchBaseProperty error:nil] floatValue]; + return (pitch - 30.0) / 35.0 * 2.0 - 1.0; +} + +int QTextToSpeechEngineOsx::volume() const +{ + return [speechSynthesizer volume] * 100; +} + +bool QTextToSpeechEngineOsx::setRate(double rate) +{ + // NSSpeechSynthesizer supports words per minute, + // human speech is 180 to 220 - use 0 to 400 as range here + [speechSynthesizer setRate: 200 + (rate * 200)]; + return true; +} + +bool QTextToSpeechEngineOsx::setVolume(int volume) +{ + [speechSynthesizer setVolume: volume / 100.0]; + return true; +} + +QLocale localeForVoice(NSString *voice) +{ + NSDictionary *attrs = [NSSpeechSynthesizer attributesForVoice:voice]; + return QString::fromNSString(attrs[NSVoiceLocaleIdentifier]); +} + +QVoice QTextToSpeechEngineOsx::voiceForNSVoice(NSString *voiceString) const +{ + NSDictionary *attrs = [NSSpeechSynthesizer attributesForVoice:voiceString]; + QString voiceName = QString::fromNSString(attrs[NSVoiceName]); + + NSString *genderString = attrs[NSVoiceGender]; + QVoice::Gender gender = [genderString isEqualToString:NSVoiceGenderMale] ? QVoice::Male : + [genderString isEqualToString:NSVoiceGenderFemale] ? QVoice::Female : + QVoice::Unknown; + + NSNumber *ageNSNumber = attrs[NSVoiceAge]; + int ageInt = ageNSNumber.intValue; + QVoice::Age age = (ageInt < 13 ? QVoice::Child : + ageInt < 20 ? QVoice::Teenager : + ageInt < 45 ? QVoice::Adult : + ageInt < 90 ? QVoice::Senior : QVoice::Other); + QVariant data = QString::fromNSString(attrs[NSVoiceIdentifier]); + QVoice voice = createVoice(voiceName, gender, age, data); + return voice; +} + +QVector<QLocale> QTextToSpeechEngineOsx::availableLocales() const +{ + return m_locales; +} + +bool QTextToSpeechEngineOsx::setLocale(const QLocale &locale) +{ + NSArray *voices = [NSSpeechSynthesizer availableVoices]; + NSString *voice = [NSSpeechSynthesizer defaultVoice]; + // always prefer default + if (locale == localeForVoice(voice)) { + [speechSynthesizer setVoice:voice]; + return true; + } + + for (voice in voices) { + QLocale voiceLocale = localeForVoice(voice); + if (locale == voiceLocale) { + [speechSynthesizer setVoice:voice]; + return true; + } + } + return false; +} + +QLocale QTextToSpeechEngineOsx::locale() const +{ + NSString *voice = [speechSynthesizer voice]; + return localeForVoice(voice); +} + +void QTextToSpeechEngineOsx::updateVoices() +{ + NSArray *voices = [NSSpeechSynthesizer availableVoices]; + for (NSString *voice in voices) { + QLocale locale = localeForVoice(voice); + QVoice data = voiceForNSVoice(voice); + if (!m_locales.contains(locale)) + m_locales.append(locale); + m_voices.insert(locale.name(), data); + } +} + +QVector<QVoice> QTextToSpeechEngineOsx::availableVoices() const +{ + return m_voices.values(locale().name()).toVector(); +} + +bool QTextToSpeechEngineOsx::setVoice(const QVoice &voice) +{ + NSString *identifier = voiceData(voice).toString().toNSString(); + [speechSynthesizer setVoice:identifier]; + return true; +} + +QVoice QTextToSpeechEngineOsx::voice() const +{ + NSString *voice = [speechSynthesizer voice]; + return voiceForNSVoice(voice); +} + +QT_END_NAMESPACE diff --git a/src/plugins/tts/osx/qtexttospeech_osx_plugin.cpp b/src/plugins/tts/osx/qtexttospeech_osx_plugin.cpp new file mode 100644 index 0000000..3dbac52 --- /dev/null +++ b/src/plugins/tts/osx/qtexttospeech_osx_plugin.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_osx_plugin.h" +#include "qtexttospeech_osx.h" + +QTextToSpeechEngine *QTextToSpeechPluginOsx::createTextToSpeechEngine(const QVariantMap ¶meters, QObject *parent, QString *errorString) const +{ + Q_UNUSED(errorString) + return new QTextToSpeechEngineOsx(parameters, parent); +} diff --git a/src/plugins/tts/osx/qtexttospeech_osx_plugin.h b/src/plugins/tts/osx/qtexttospeech_osx_plugin.h new file mode 100644 index 0000000..36cdb3a --- /dev/null +++ b/src/plugins/tts/osx/qtexttospeech_osx_plugin.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHPLUGIN_OSX_H +#define QTEXTTOSPEECHPLUGIN_OSX_H + +#include <QtCore/QObject> +#include <QtCore/QLoggingCategory> +#include <QtTextToSpeech/qtexttospeechplugin.h> +#include <QtTextToSpeech/qtexttospeechengine.h> + +QT_BEGIN_NAMESPACE + +class QTextToSpeechPluginOsx : public QObject, public QTextToSpeechPlugin +{ + Q_OBJECT + Q_INTERFACES(QTextToSpeechPlugin) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.speech.tts.plugin/5.0" + FILE "osx_plugin.json") + +public: + QTextToSpeechEngine *createTextToSpeechEngine( + const QVariantMap ¶meters, + QObject *parent, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/sapi/qtexttospeech_sapi.cpp b/src/plugins/tts/sapi/qtexttospeech_sapi.cpp new file mode 100644 index 0000000..7df3399 --- /dev/null +++ b/src/plugins/tts/sapi/qtexttospeech_sapi.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_sapi.h" + +#include <windows.h> +#include <sapi.h> +#include <sphelper.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +QTextToSpeechEngineSapi::QTextToSpeechEngineSapi(const QVariantMap &, QObject *) + : m_pitch(0.0), m_pauseCount(0), m_state(QTextToSpeech::BackendError) +{ + init(); +} + +QTextToSpeechEngineSapi::~QTextToSpeechEngineSapi() +{ +} + +void QTextToSpeechEngineSapi::init() +{ + if (FAILED(::CoInitialize(NULL))) { + qWarning() << "Init of COM failed"; + return; + } + + HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&m_voice); + if (!SUCCEEDED(hr)) { + qWarning() << "Could not init voice"; + return; + } + + m_voice->SetInterest(SPFEI_ALL_TTS_EVENTS, SPFEI_ALL_TTS_EVENTS); + m_voice->SetNotifyCallbackInterface(this, 0, 0); + updateVoices(); + m_state = QTextToSpeech::Ready; +} + +bool QTextToSpeechEngineSapi::isSpeaking() const +{ + SPVOICESTATUS eventStatus; + m_voice->GetStatus(&eventStatus, NULL); + return eventStatus.dwRunningState == SPRS_IS_SPEAKING; +} + +void QTextToSpeechEngineSapi::say(const QString &text) +{ + if (text.isEmpty()) + return; + + QString textString = text; + if (m_state != QTextToSpeech::Ready) + stop(); + + textString.prepend(QString::fromLatin1("<pitch absmiddle=\"%1\"/>").arg(m_pitch * 10)); + + std::wstring wtext = textString.toStdWString(); + m_voice->Speak(wtext.data(), SPF_ASYNC, NULL); +} + +void QTextToSpeechEngineSapi::stop() +{ + if (m_state == QTextToSpeech::Paused) + resume(); + m_voice->Speak(NULL, SPF_PURGEBEFORESPEAK, 0); +} + +void QTextToSpeechEngineSapi::pause() +{ + if (!isSpeaking()) + return; + + if (m_pauseCount == 0) { + ++m_pauseCount; + m_voice->Pause(); + m_state = QTextToSpeech::Paused; + emit stateChanged(m_state); + } +} + +void QTextToSpeechEngineSapi::resume() +{ + if (m_pauseCount > 0) { + --m_pauseCount; + m_voice->Resume(); + if (isSpeaking()) { + m_state = QTextToSpeech::Speaking; + } else { + m_state = QTextToSpeech::Ready; + } + emit stateChanged(m_state); + } +} + +bool QTextToSpeechEngineSapi::setPitch(double pitch) +{ + m_pitch = pitch; + return true; +} + +double QTextToSpeechEngineSapi::pitch() const +{ + return m_pitch; +} + +bool QTextToSpeechEngineSapi::setRate(double rate) +{ + // -10 to 10 + m_voice->SetRate(long(rate*10)); + return true; +} + +double QTextToSpeechEngineSapi::rate() const +{ + long rateValue; + if (m_voice->GetRate(&rateValue) == S_OK) + return rateValue / 10.0; + return -1; +} + +bool QTextToSpeechEngineSapi::setVolume(int volume) +{ + // 0 to 100 + m_voice->SetVolume(volume); + return true; +} + +int QTextToSpeechEngineSapi::volume() const +{ + USHORT baseVolume; + if (m_voice->GetVolume(&baseVolume) == S_OK) + { + return baseVolume; + } + return -1; +} + +QString QTextToSpeechEngineSapi::voiceId(ISpObjectToken *speechToken) const +{ + HRESULT hr = S_OK; + LPWSTR vId = nullptr; + hr = speechToken->GetId(&vId); + if (FAILED(hr)) { + qWarning() << "ISpObjectToken::GetId failed"; + return QString(); + } + return QString::fromWCharArray(vId); +} + +QMap<QString, QString> QTextToSpeechEngineSapi::voiceAttributes(ISpObjectToken *speechToken) const +{ + HRESULT hr = S_OK; + QMap<QString, QString> result; + + ISpDataKey *pAttrKey = nullptr; + hr = speechToken->OpenKey(L"Attributes", &pAttrKey); + if (FAILED(hr)) { + qWarning() << "ISpObjectToken::OpenKeys failed"; + return result; + } + + // enumerate values + for (ULONG v = 0; ; v++) { + LPWSTR val = nullptr; + hr = pAttrKey->EnumValues(v, &val); + if (SPERR_NO_MORE_ITEMS == hr) { + // done + break; + } else if (FAILED(hr)) { + qWarning() << "ISpDataKey::EnumValues failed"; + continue; + } + + // how do we know whether it's a string or a DWORD? + LPWSTR data = nullptr; + hr = pAttrKey->GetStringValue(val, &data); + if (FAILED(hr)) { + qWarning() << "ISpDataKey::GetStringValue failed"; + continue; + } + + if (0 != wcscmp(val, L"")) { + result[QString::fromWCharArray(val)] = QString::fromWCharArray(data); + } + + // FIXME: Do we need to free the memory here? + CoTaskMemFree(val); + CoTaskMemFree(data); + } + + return result; +} + +QLocale QTextToSpeechEngineSapi::lcidToLocale(const QString &lcid) const +{ + bool ok; + LCID locale = lcid.toInt(&ok, 16); + if (!ok) { + qWarning() << "Could not convert language attribute to LCID"; + return QLocale(); + } + int nchars = GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, NULL, 0); + wchar_t* languageCode = new wchar_t[nchars]; + GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, languageCode, nchars); + QString iso = QString::fromWCharArray(languageCode); + delete[] languageCode; + return QLocale(iso); +} + +void QTextToSpeechEngineSapi::updateVoices() +{ + HRESULT hr = S_OK; + CComPtr<ISpObjectToken> cpVoiceToken; + CComPtr<IEnumSpObjectTokens> cpEnum; + ULONG ulCount = 0; + + hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum); + if (SUCCEEDED(hr)) { + hr = cpEnum->GetCount(&ulCount); + } + + // Loop through all voices + while (SUCCEEDED(hr) && ulCount--) { + cpVoiceToken.Release(); + hr = cpEnum->Next(1, &cpVoiceToken, NULL); + + // Get attributes of the voice + QMap<QString, QString> vAttr = voiceAttributes(cpVoiceToken); + + // Transform Windows LCID to QLocale + QLocale vLocale = lcidToLocale(vAttr["Language"]); + if (!m_locales.contains(vLocale)) + m_locales.append(vLocale); + + // Create voice + + QString name = vAttr["Name"]; + QVoice::Age age = vAttr["Age"] == "Adult" ? QVoice::Adult : QVoice::Other; + QVoice::Gender gender = vAttr["Gender"] == "Male" ? QVoice::Male : + vAttr["Gender"] == "Female" ? QVoice::Female : + QVoice::Unknown; + // Getting the ID of the voice to set the voice later + QString vId = voiceId(cpVoiceToken); + QVoice voice = createVoice(name, gender, age, vId); + m_voices.insert(vLocale.name(), voice); + } +} + +QVector<QLocale> QTextToSpeechEngineSapi::availableLocales() const +{ + return m_locales; +} + +bool QTextToSpeechEngineSapi::setLocale(const QLocale &locale) +{ + QList<QVoice> voicesForLocale = m_voices.values(locale.name()); + if (voicesForLocale.length() > 0) { + setVoice(voicesForLocale[0]); + return true; + } else { + qWarning() << "No voice found for given locale"; + } + return false; +} + +QLocale QTextToSpeechEngineSapi::locale() const +{ + // Get current voice id + CComPtr<ISpObjectToken> cpVoiceToken; + m_voice->GetVoice(&cpVoiceToken); + // read attributes + QMap<QString, QString> vAttr = voiceAttributes(cpVoiceToken); + return lcidToLocale(vAttr["Language"]); +} + +QVector<QVoice> QTextToSpeechEngineSapi::availableVoices() const +{ + return m_voices.values(locale().name()).toVector(); +} + +bool QTextToSpeechEngineSapi::setVoice(const QVoice &voice) +{ + // Convert voice id to null-terminated wide char string + QString vId = voiceData(voice).toString(); + wchar_t* tokenId = new wchar_t[vId.size()+1]; + vId.toWCharArray(tokenId); + tokenId[vId.size()] = 0; + + // create the voice token via the id + HRESULT hr = S_OK; + CComPtr<ISpObjectToken> cpVoiceToken; + hr = SpCreateNewToken(tokenId, &cpVoiceToken); + if (FAILED(hr)) { + qWarning() << "Creating the voice token from ID failed"; + m_state = QTextToSpeech::BackendError; + emit stateChanged(m_state); + return false; + } + + if (m_state != QTextToSpeech::Ready) { + m_state = QTextToSpeech::Ready; + emit stateChanged(m_state); + } + + delete[] tokenId; + m_voice->SetVoice(cpVoiceToken); + return true; +} + +QVoice QTextToSpeechEngineSapi::voice() const +{ + CComPtr<ISpObjectToken> cpVoiceToken; + m_voice->GetVoice(&cpVoiceToken); + QString vId = voiceId(cpVoiceToken); + foreach (const QVoice &voice, m_voices.values()) { + if (voiceData(voice).toString() == vId) { + return voice; + } + } + return QVoice(); +} + +QTextToSpeech::State QTextToSpeechEngineSapi::state() const +{ + return m_state; +} + +HRESULT QTextToSpeechEngineSapi::NotifyCallback(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + QTextToSpeech::State newState = QTextToSpeech::Ready; + if (isPaused()) { + newState = QTextToSpeech::Paused; + } else if (isSpeaking()) { + newState = QTextToSpeech::Speaking; + } else { + newState = QTextToSpeech::Ready; + } + + if (m_state != newState) { + m_state = newState; + emit stateChanged(newState); + } + + return S_OK; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tts/sapi/qtexttospeech_sapi.h b/src/plugins/tts/sapi/qtexttospeech_sapi.h new file mode 100644 index 0000000..341a75f --- /dev/null +++ b/src/plugins/tts/sapi/qtexttospeech_sapi.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHENGINE_SAPI_H +#define QTEXTTOSPEECHENGINE_SAPI_H + +#include <windows.h> +#include <sapi.h> +#include <sphelper.h> + +#include <QtCore/qobject.h> +#include <QtCore/qvector.h> +#include <QtCore/qstring.h> +#include <QtCore/qlocale.h> +#include <QtTextToSpeech/qtexttospeechengine.h> +#include <QtTextToSpeech/qvoice.h> + +QT_BEGIN_NAMESPACE + +class QTextToSpeechEngineSapi : public QTextToSpeechEngine, public ISpNotifyCallback +{ + Q_OBJECT + +public: + QTextToSpeechEngineSapi(const QVariantMap ¶meters, QObject *parent); + ~QTextToSpeechEngineSapi(); + + // Plug-in API: + QVector<QLocale> availableLocales() const Q_DECL_OVERRIDE; + QVector<QVoice> availableVoices() const Q_DECL_OVERRIDE; + void say(const QString &text) Q_DECL_OVERRIDE; + void stop() Q_DECL_OVERRIDE; + void pause() Q_DECL_OVERRIDE; + void resume() Q_DECL_OVERRIDE; + double rate() const Q_DECL_OVERRIDE; + bool setRate(double rate) Q_DECL_OVERRIDE; + double pitch() const Q_DECL_OVERRIDE; + bool setPitch(double pitch) Q_DECL_OVERRIDE; + QLocale locale() const Q_DECL_OVERRIDE; + bool setLocale(const QLocale &locale) Q_DECL_OVERRIDE; + int volume() const Q_DECL_OVERRIDE; + bool setVolume(int volume) Q_DECL_OVERRIDE; + QVoice voice() const Q_DECL_OVERRIDE; + bool setVoice(const QVoice &voice) Q_DECL_OVERRIDE; + QTextToSpeech::State state() const Q_DECL_OVERRIDE; + + HRESULT STDMETHODCALLTYPE NotifyCallback(WPARAM /*wParam*/, LPARAM /*lParam*/); +private: + + void init(); + bool isSpeaking() const; + bool isPaused() const { return m_pauseCount; } + QMap<QString, QString> voiceAttributes(ISpObjectToken *speechToken) const; + QString voiceId(ISpObjectToken *speechToken) const; + QLocale lcidToLocale(const QString &lcid) const; + void updateVoices(); + + QTextToSpeech::State m_state; + QVector<QLocale> m_locales; +// QLocale m_currentLocale; + QVoice m_currentVoice; + // Voices mapped by their locale name. + QMultiMap<QString, QVoice> m_voices; + + ISpVoice *m_voice; + double m_pitch; + int m_pauseCount; +}; +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/sapi/qtexttospeech_sapi_plugin.cpp b/src/plugins/tts/sapi/qtexttospeech_sapi_plugin.cpp new file mode 100644 index 0000000..b22126d --- /dev/null +++ b/src/plugins/tts/sapi/qtexttospeech_sapi_plugin.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_sapi_plugin.h" +#include "qtexttospeech_sapi.h" + +QTextToSpeechEngine *QTextToSpeechPluginSapi::createTextToSpeechEngine(const QVariantMap ¶meters, QObject *parent, QString *errorString) const +{ + Q_UNUSED(errorString) + return new QTextToSpeechEngineSapi(parameters, parent); +} diff --git a/src/plugins/tts/sapi/qtexttospeech_sapi_plugin.h b/src/plugins/tts/sapi/qtexttospeech_sapi_plugin.h new file mode 100644 index 0000000..8d6b75b --- /dev/null +++ b/src/plugins/tts/sapi/qtexttospeech_sapi_plugin.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHPLUGIN_SAPI_H +#define QTEXTTOSPEECHPLUGIN_SAPI_H + +#include <QtCore/QObject> +#include <QtCore/QLoggingCategory> +#include <QtTextToSpeech/qtexttospeechplugin.h> +#include <QtTextToSpeech/qtexttospeechengine.h> + +QT_BEGIN_NAMESPACE + +class QTextToSpeechPluginSapi : public QObject, public QTextToSpeechPlugin +{ + Q_OBJECT + Q_INTERFACES(QTextToSpeechPlugin) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.speech.tts.plugin/5.0" + FILE "sapi_plugin.json") + +public: + QTextToSpeechEngine *createTextToSpeechEngine( + const QVariantMap ¶meters, + QObject *parent, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/sapi/sapi.pro b/src/plugins/tts/sapi/sapi.pro new file mode 100644 index 0000000..b78641d --- /dev/null +++ b/src/plugins/tts/sapi/sapi.pro @@ -0,0 +1,26 @@ +TARGET = qtexttospeech_sapi +PLUGIN_TYPE = texttospeech +PLUGIN_CLASS_NAME = QTextToSpeechPluginSapi + +# sapi.h contains some parts that fail with "strictStrings" +QMAKE_CFLAGS_RELEASE -= -Zc:strictStrings +QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO -= -Zc:strictStrings +QMAKE_CXXFLAGS_RELEASE -= -Zc:strictStrings +QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO -= -Zc:strictStrings +QMAKE_CFLAGS -= -Zc:strictStrings +QMAKE_CXXFLAGS -= -Zc:strictStrings + +load(qt_plugin) + +QT += core texttospeech + +HEADERS += \ + qtexttospeech_sapi.h \ + qtexttospeech_sapi_plugin.h \ + +SOURCES += \ + qtexttospeech_sapi.cpp \ + qtexttospeech_sapi_plugin.cpp \ + +OTHER_FILES += \ + sapi_plugin.json diff --git a/src/plugins/tts/sapi/sapi_plugin.json b/src/plugins/tts/sapi/sapi_plugin.json new file mode 100644 index 0000000..84e1e48 --- /dev/null +++ b/src/plugins/tts/sapi/sapi_plugin.json @@ -0,0 +1,6 @@ +{ + "Keys": ["sapi"], + "Provider": "sapi", + "Version": 100, + "Features": [] +} diff --git a/src/plugins/tts/speechdispatcher/speechdispatcher.pro b/src/plugins/tts/speechdispatcher/speechdispatcher.pro index 6f4e5cc..5a5c0ab 100644 --- a/src/plugins/tts/speechdispatcher/speechdispatcher.pro +++ b/src/plugins/tts/speechdispatcher/speechdispatcher.pro @@ -6,8 +6,8 @@ load(qt_plugin) QT += core texttospeech +CONFIG += link_pkgconfig PKGCONFIG = speech-dispatcher -LIBS += -lspeechd HEADERS += \ qtexttospeech_speechd.h \ diff --git a/src/plugins/tts/tts.pro b/src/plugins/tts/tts.pro index ef07835..90b79a6 100755..100644 --- a/src/plugins/tts/tts.pro +++ b/src/plugins/tts/tts.pro @@ -6,3 +6,18 @@ unix { SUBDIRS += speechdispatcher } } + +# mingw needs copies of the structures defined in sapi.h +# until those are written, disable the sapi plugin for mingw +windows:!winrt:!mingw: SUBDIRS += sapi +winrt: SUBDIRS += winrt + +osx: SUBDIRS += osx + +config_flite { + SUBDIRS += flite +} + +config_vocalizer: exists(vocalizer) { + SUBDIRS += vocalizer +} diff --git a/src/plugins/tts/winrt/qtexttospeech_winrt.cpp b/src/plugins/tts/winrt/qtexttospeech_winrt.cpp new file mode 100644 index 0000000..583cb6f --- /dev/null +++ b/src/plugins/tts/winrt/qtexttospeech_winrt.cpp @@ -0,0 +1,448 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_winrt.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/qfunctions_winrt.h> +#include <QtCore/QMap> +#include <QtCore/QTimer> +#include <private/qeventdispatcher_winrt_p.h> + +#include <windows.foundation.h> +#include <windows.foundation.collections.h> +#include <windows.media.speechsynthesis.h> +#include <windows.storage.streams.h> +#include <windows.ui.xaml.h> +#include <windows.ui.xaml.controls.h> +#include <windows.ui.xaml.markup.h> + +#include <functional> +#include <wrl.h> + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Media::SpeechSynthesis; +using namespace ABI::Windows::Storage::Streams; +using namespace ABI::Windows::UI::Xaml; +using namespace ABI::Windows::UI::Xaml::Controls; +using namespace ABI::Windows::UI::Xaml::Markup; +using namespace ABI::Windows::UI::Xaml::Media; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +QT_BEGIN_NAMESPACE + +#define LSTRING(str) L#str +static const wchar_t webviewXaml[] = LSTRING( +<MediaElement xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" /> +); + +class QTextToSpeechEngineWinRTPrivate +{ +public: + QTimer timer; + ComPtr<IXamlReaderStatics> xamlReader; + ComPtr<ISpeechSynthesizer> synth; + QVector<QLocale> locales; + QVector<QVoice> voices; + QVector<ComPtr<IVoiceInformation>> infos; + EventRegistrationToken tok; + + ComPtr<IMediaElement> media; + + double rate; + double volume; + + QTextToSpeech::State state; +}; + +QTextToSpeechEngineWinRT::QTextToSpeechEngineWinRT(const QVariantMap &, QObject *parent) + : QTextToSpeechEngine(parent) + , d_ptr(new QTextToSpeechEngineWinRTPrivate) +{ + d_ptr->rate = 0; + d_ptr->volume = 100; + d_ptr->timer.setInterval(100); + connect(&d_ptr->timer, &QTimer::timeout, this, &QTextToSpeechEngineWinRT::checkElementState); + + init(); +} + +QTextToSpeechEngineWinRT::~QTextToSpeechEngineWinRT() +{ +} + +QVector<QLocale> QTextToSpeechEngineWinRT::availableLocales() const +{ + Q_D(const QTextToSpeechEngineWinRT); + return d->locales; +} + +QVector<QVoice> QTextToSpeechEngineWinRT::availableVoices() const +{ + Q_D(const QTextToSpeechEngineWinRT); + return d->voices; +} + +void QTextToSpeechEngineWinRT::say(const QString &text) +{ + Q_D(QTextToSpeechEngineWinRT); + + HRESULT hr; + + hr = QEventDispatcherWinRT::runOnXamlThread([text, d]() { + HRESULT hr; + HStringReference nativeText(reinterpret_cast<LPCWSTR>(text.utf16()), text.length()); + ComPtr<IAsyncOperation<SpeechSynthesisStream*>> op; + + hr = d->synth->SynthesizeTextToStreamAsync(nativeText.Get(), &op); + RETURN_HR_IF_FAILED("Could not synthesize text."); + + ComPtr<ISpeechSynthesisStream> stream; + hr = QWinRTFunctions::await(op, stream.GetAddressOf()); + RETURN_HR_IF_FAILED("Synthesizing failed."); + + ComPtr<IRandomAccessStream> randomStream; + hr = stream.As(&randomStream); + RETURN_HR_IF_FAILED("Could not cast to RandomAccessStream."); + + // Directly instantiating a MediaElement works, but it throws an exception + // when setting the source. Using a XamlReader appears to set it up properly. + ComPtr<IInspectable> element; + hr = d->xamlReader->Load(HString::MakeReference(webviewXaml).Get(), &element); + Q_ASSERT_SUCCEEDED(hr); + + if (d->media) + d->media.Reset(); + + hr = element.As(&d->media); + RETURN_HR_IF_FAILED("Could not create MediaElement for playback."); + + // Volume and Playback Rate cannot be changed for synthesized audio once + // it has been created. Hence QTextToSpeechEngineWinRT::setVolume/Rate + // only cache the value until playback is started. + hr = d->media->put_DefaultPlaybackRate(d->rate + 1); + if (FAILED(hr)) + qWarning("Could not set playback rate."); + + const DOUBLE vol = DOUBLE(d->volume) / 100.; + hr = d->media->put_Volume(vol); + if (FAILED(hr)) + qWarning("Could not set volume."); + + static const HStringReference empty(L""); + hr = d->media->SetSource(randomStream.Get(), empty.Get()); + RETURN_HR_IF_FAILED("Could not set media source."); + + hr = d->media->Play(); + RETURN_HR_IF_FAILED("Could not initiate playback."); + + return S_OK; + }); + if (SUCCEEDED(hr)) { + d->timer.start(); + d->state = QTextToSpeech::Speaking; + } else { + d->state = QTextToSpeech::BackendError; + } + emit stateChanged(d->state); +} + +void QTextToSpeechEngineWinRT::stop() +{ + Q_D(QTextToSpeechEngineWinRT); + + if (!d->media) + return; + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([d]() { + HRESULT hr = d->media->Stop(); + RETURN_HR_IF_FAILED("Could not stop playback."); + + d->media.Reset(); + return hr; + }); + if (SUCCEEDED(hr)) { + d->timer.stop(); + d->state = QTextToSpeech::Ready; + emit stateChanged(d->state); + } +} + +void QTextToSpeechEngineWinRT::pause() +{ + Q_D(QTextToSpeechEngineWinRT); + + if (!d->media) + return; + + // Stop timer first to not have checkElementState being invoked + // while context switch to/from Xaml thread happens. + d->timer.stop(); + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([d]() { + HRESULT hr = d->media->Pause(); + RETURN_HR_IF_FAILED("Could not pause playback."); + return hr; + }); + if (SUCCEEDED(hr)) { + d->state = QTextToSpeech::Paused; + emit stateChanged(d->state); + } +} + +void QTextToSpeechEngineWinRT::resume() +{ + Q_D(QTextToSpeechEngineWinRT); + + if (!d->media) + return; + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([d]() { + HRESULT hr = d->media->Play(); + RETURN_HR_IF_FAILED("Could not resume playback."); + return hr; + }); + if (SUCCEEDED(hr)) { + d->timer.start(); + d->state = QTextToSpeech::Speaking; + emit stateChanged(d->state); + } +} + +double QTextToSpeechEngineWinRT::rate() const +{ + Q_D(const QTextToSpeechEngineWinRT); + + return d->rate; +} + +bool QTextToSpeechEngineWinRT::setRate(double rate) +{ + Q_D(QTextToSpeechEngineWinRT); + + d->rate = rate; + return true; +} + +double QTextToSpeechEngineWinRT::pitch() const +{ + // Not supported for WinRT + Q_UNIMPLEMENTED(); + return 1.; +} + +bool QTextToSpeechEngineWinRT::setPitch(double pitch) +{ + // Not supported for WinRT + Q_UNUSED(pitch); + Q_UNIMPLEMENTED(); + return false; +} + +QLocale QTextToSpeechEngineWinRT::locale() const +{ + Q_D(const QTextToSpeechEngineWinRT); + + HRESULT hr; + ComPtr<IVoiceInformation> info; + hr = d->synth->get_Voice(&info); + + HString language; + hr = info->get_Language(language.GetAddressOf()); + + return QLocale(QString::fromWCharArray(language.GetRawBuffer(0))); +} + +bool QTextToSpeechEngineWinRT::setLocale(const QLocale &locale) +{ + Q_D(QTextToSpeechEngineWinRT); + + const int index = d->locales.indexOf(locale); + if (index == -1) + return false; + + return setVoice(d->voices.at(index)); +} + +int QTextToSpeechEngineWinRT::volume() const +{ + Q_D(const QTextToSpeechEngineWinRT); + + return d->volume; +} + +bool QTextToSpeechEngineWinRT::setVolume(int volume) +{ + Q_D(QTextToSpeechEngineWinRT); + + d->volume = volume; + return true; +} + +QVoice QTextToSpeechEngineWinRT::voice() const +{ + Q_D(const QTextToSpeechEngineWinRT); + + HRESULT hr; + ComPtr<IVoiceInformation> info; + hr = d->synth->get_Voice(&info); + + return createVoiceForInformation(info); +} + +bool QTextToSpeechEngineWinRT::setVoice(const QVoice &voice) +{ + Q_D(QTextToSpeechEngineWinRT); + + const int index = d->voices.indexOf(voice); + if (index == -1) + return false; + + HRESULT hr; + hr = d->synth->put_Voice(d->infos.at(index).Get()); + return SUCCEEDED(hr); +} + +QTextToSpeech::State QTextToSpeechEngineWinRT::state() const +{ + Q_D(const QTextToSpeechEngineWinRT); + return d->state; +} + +void QTextToSpeechEngineWinRT::checkElementState() +{ + Q_D(QTextToSpeechEngineWinRT); + + // MediaElement does not move into Stopped or Closed state when it finished + // playback of synthesised text. Instead it goes into Pause mode. + // Because of this MediaElement::add_MediaEnded() is not invoked and we + // cannot add an event listener to the Media Element to properly emit + // state changes. + // To still be able to capture when it is ready, use a periodic timer and + // check if the MediaElement went into Pause state. + bool finished = false; + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([d, &finished]() { + HRESULT hr; + ABI::Windows::UI::Xaml::Media::MediaElementState s; + hr = d->media.Get()->get_CurrentState(&s); + if (SUCCEEDED(hr) && s == MediaElementState_Paused) + finished = true; + return hr; + }); + + if (finished) + stop(); +} + +void QTextToSpeechEngineWinRT::init() +{ + Q_D(QTextToSpeechEngineWinRT); + + d->state = QTextToSpeech::BackendError; + + HRESULT hr; + + hr = QEventDispatcherWinRT::runOnXamlThread([d]() { + HRESULT hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Xaml_Markup_XamlReader).Get(), + IID_PPV_ARGS(&d->xamlReader)); + Q_ASSERT_SUCCEEDED(hr); + + return hr; + }); + + ComPtr<IInstalledVoicesStatic> stat; + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_SpeechSynthesis_SpeechSynthesizer).Get(), + IID_PPV_ARGS(&stat)); + Q_ASSERT_SUCCEEDED(hr); + + hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_SpeechSynthesis_SpeechSynthesizer).Get(), + &d->synth); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IVectorView<VoiceInformation*>> voices; + hr = stat->get_AllVoices(&voices); + RETURN_VOID_IF_FAILED("Could not get voice information."); + + quint32 voiceSize; + hr = voices->get_Size(&voiceSize); + RETURN_VOID_IF_FAILED("Could not access size of voice information."); + + for (quint32 i = 0; i < voiceSize; ++i) { + ComPtr<IVoiceInformation> info; + hr = voices->GetAt(i, &info); + Q_ASSERT_SUCCEEDED(hr); + + HString nativeLanguage; + hr = info->get_Language(nativeLanguage.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + const QString languageString = QString::fromWCharArray(nativeLanguage.GetRawBuffer(0)); + QLocale locale(languageString); + if (!d->locales.contains(locale)) + d->locales.append(locale); + + QVoice voice = createVoiceForInformation(info); + d->voices.append(voice); + d->infos.append(info); + } + + d->state = QTextToSpeech::Ready; +} + +QVoice QTextToSpeechEngineWinRT::createVoiceForInformation(ComPtr<IVoiceInformation> info) const +{ + HRESULT hr; + HString nativeName; + hr = info->get_DisplayName(nativeName.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + const QString name = QString::fromWCharArray(nativeName.GetRawBuffer(0)); + + VoiceGender gender; + hr = info->get_Gender(&gender); + Q_ASSERT_SUCCEEDED(hr); + + return QTextToSpeechEngine::createVoice(name, gender == VoiceGender_Male ? QVoice::Male : QVoice::Female, + QVoice::Other, QVariant()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/tts/winrt/qtexttospeech_winrt.h b/src/plugins/tts/winrt/qtexttospeech_winrt.h new file mode 100644 index 0000000..a39fc08 --- /dev/null +++ b/src/plugins/tts/winrt/qtexttospeech_winrt.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHENGINE_WINRT_H +#define QTEXTTOSPEECHENGINE_WINRT_H + +#include <QtTextToSpeech/qtexttospeechengine.h> +#include <QtTextToSpeech/qvoice.h> + +#include <QtCore/QObject> +#include <QtCore/QVector> +#include <QtCore/QString> +#include <QtCore/QLocale> +#include <QtCore/QScopedPointer> +#include <QtCore/qt_windows.h> +#include <wrl.h> + +namespace ABI { + namespace Windows { + namespace Media { + namespace SpeechSynthesis { + struct IVoiceInformation; + } + } + } +} + +QT_BEGIN_NAMESPACE + +class QTextToSpeechEngineWinRTPrivate; + +class QTextToSpeechEngineWinRT : public QTextToSpeechEngine +{ + Q_OBJECT + +public: + QTextToSpeechEngineWinRT(const QVariantMap ¶meters, QObject *parent); + ~QTextToSpeechEngineWinRT(); + + QVector<QLocale> availableLocales() const Q_DECL_OVERRIDE; + QVector<QVoice> availableVoices() const Q_DECL_OVERRIDE; + void say(const QString &text) Q_DECL_OVERRIDE; + void stop() Q_DECL_OVERRIDE; + void pause() Q_DECL_OVERRIDE; + void resume() Q_DECL_OVERRIDE; + double rate() const Q_DECL_OVERRIDE; + bool setRate(double rate) Q_DECL_OVERRIDE; + double pitch() const Q_DECL_OVERRIDE; + bool setPitch(double pitch) Q_DECL_OVERRIDE; + QLocale locale() const Q_DECL_OVERRIDE; + bool setLocale(const QLocale &locale) Q_DECL_OVERRIDE; + int volume() const Q_DECL_OVERRIDE; + bool setVolume(int volume) Q_DECL_OVERRIDE; + QVoice voice() const Q_DECL_OVERRIDE; + bool setVoice(const QVoice &voice) Q_DECL_OVERRIDE; + QTextToSpeech::State state() const Q_DECL_OVERRIDE; + +public slots: + void checkElementState(); +private: + void init(); + QVoice createVoiceForInformation(Microsoft::WRL::ComPtr<ABI::Windows::Media::SpeechSynthesis::IVoiceInformation> info) const; + + QScopedPointer<QTextToSpeechEngineWinRTPrivate> d_ptr; + Q_DECLARE_PRIVATE(QTextToSpeechEngineWinRT) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/winrt/qtexttospeech_winrt_plugin.cpp b/src/plugins/tts/winrt/qtexttospeech_winrt_plugin.cpp new file mode 100644 index 0000000..16e8050 --- /dev/null +++ b/src/plugins/tts/winrt/qtexttospeech_winrt_plugin.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtexttospeech_winrt_plugin.h" +#include "qtexttospeech_winrt.h" + +QTextToSpeechEngine *QTextToSpeechPluginWinRT::createTextToSpeechEngine(const QVariantMap ¶meters, QObject *parent, QString *errorString) const +{ + Q_UNUSED(errorString) + return new QTextToSpeechEngineWinRT(parameters, parent); +} diff --git a/src/plugins/tts/winrt/qtexttospeech_winrt_plugin.h b/src/plugins/tts/winrt/qtexttospeech_winrt_plugin.h new file mode 100644 index 0000000..4f92651 --- /dev/null +++ b/src/plugins/tts/winrt/qtexttospeech_winrt_plugin.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Speech module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTTOSPEECHPLUGIN_WINRT_H +#define QTEXTTOSPEECHPLUGIN_WINRT_H + +#include <QtCore/QObject> +#include <QtCore/QLoggingCategory> +#include <QtTextToSpeech/qtexttospeechplugin.h> +#include <QtTextToSpeech/qtexttospeechengine.h> + +QT_BEGIN_NAMESPACE + +class QTextToSpeechPluginWinRT : public QObject, public QTextToSpeechPlugin +{ + Q_OBJECT + Q_INTERFACES(QTextToSpeechPlugin) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.speech.tts.plugin/5.0" + FILE "winrt_plugin.json") + +public: + QTextToSpeechEngine *createTextToSpeechEngine( + const QVariantMap ¶meters, + QObject *parent, + QString *errorString) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tts/winrt/winrt.pro b/src/plugins/tts/winrt/winrt.pro new file mode 100644 index 0000000..6896620 --- /dev/null +++ b/src/plugins/tts/winrt/winrt.pro @@ -0,0 +1,18 @@ +TARGET = qtexttospeech_winrt +PLUGIN_TYPE = texttospeech +PLUGIN_CLASS_NAME = QTextToSpeechPluginWinRT + +load(qt_plugin) + +QT += core core-private texttospeech + +HEADERS += \ + qtexttospeech_winrt.h \ + qtexttospeech_winrt_plugin.h \ + +SOURCES += \ + qtexttospeech_winrt.cpp \ + qtexttospeech_winrt_plugin.cpp \ + +OTHER_FILES += \ + winrt_plugin.json diff --git a/src/plugins/tts/winrt/winrt_plugin.json b/src/plugins/tts/winrt/winrt_plugin.json new file mode 100644 index 0000000..1f54076 --- /dev/null +++ b/src/plugins/tts/winrt/winrt_plugin.json @@ -0,0 +1,6 @@ +{ + "Keys": ["winrt"], + "Provider": "winrt", + "Version": 100, + "Features": [] +} |