diff --git a/articleview.cc b/articleview.cc index 7464e5a3..17f5771f 100644 --- a/articleview.cc +++ b/articleview.cc @@ -973,7 +973,7 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, if ( itemMd5Id == md5Id ) { - SpeechClient * speechClient = new SpeechClient( i->id, this ); + SpeechClient * speechClient = new SpeechClient( *i, this ); connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) ); speechClient->tell( text ); break; diff --git a/config.cc b/config.cc index 2e600941..ea17d0b7 100644 --- a/config.cc +++ b/config.cc @@ -604,7 +604,7 @@ Class load() throw( exError ) QDomNode ves = root.namedItem( "voiceEngines" ); - if ( !wss.isNull() ) + if ( !ves.isNull() ) { QDomNodeList nl = ves.toElement().elementsByTagName( "voiceEngine" ); @@ -617,6 +617,12 @@ Class load() throw( exError ) v.id = ve.attribute( "id" ); v.name = ve.attribute( "name" ); v.iconFilename = ve.attribute( "icon" ); + v.volume = ve.attribute( "volume", "50" ).toInt(); + if( v.volume < 0 || v.volume > 100 ) + v.volume = 50; + v.rate = ve.attribute( "rate", "50" ).toInt(); + if( v.rate < 0 || v.rate > 100 ) + v.rate = 50; c.voiceEngines.push_back( v ); } } @@ -1205,6 +1211,14 @@ void save( Class const & c ) throw( exError ) QDomAttr icon = dd.createAttribute( "icon" ); icon.setValue( i->iconFilename ); v.setAttributeNode( icon ); + + QDomAttr volume = dd.createAttribute( "volume" ); + volume.setValue( QString::number( i->volume ) ); + v.setAttributeNode( volume ); + + QDomAttr rate = dd.createAttribute( "rate" ); + rate.setValue( QString::number( i->rate ) ); + v.setAttributeNode( rate ); } } diff --git a/config.hh b/config.hh index dfb0ceef..596df241 100644 --- a/config.hh +++ b/config.hh @@ -383,25 +383,37 @@ typedef QVector< Program > Programs; struct VoiceEngine { - bool enabled; - QString id; - QString name; + bool enabled; + QString id; + QString name; QString iconFilename; + int volume; // 0-100 allowed + int rate; // 0-100 allowed - VoiceEngine(): enabled( false ) + VoiceEngine(): enabled( false ) + , volume( 50 ) + , rate( 50 ) + {} + VoiceEngine( QString id_, QString name_, int volume_, int rate_ ): + enabled( false ) + , id( id_ ) + , name( name_ ) + , volume( volume_ ) + , rate( rate_ ) + {} + + bool operator == ( VoiceEngine const & other ) const { - } - - bool operator == ( VoiceEngine const & other ) const - { - return enabled == other.enabled && + return enabled == other.enabled && id == other.id && name == other.name && - iconFilename == other.iconFilename; - } + iconFilename == other.iconFilename && + volume == other.volume && + rate == other.rate; + } - bool operator != ( VoiceEngine const & other ) const - { return ! operator == ( other ); } + bool operator != ( VoiceEngine const & other ) const + { return ! operator == ( other ); } }; typedef QVector< VoiceEngine> VoiceEngines; diff --git a/goldendict.pro b/goldendict.pro index 541822bc..da962ac7 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -348,7 +348,8 @@ win32 { texttospeechsource.hh \ sapi.hh \ sphelper.hh \ - speechclient.hh + speechclient.hh \ + speechhlp.hh } mac { diff --git a/speechclient.hh b/speechclient.hh index 2c744706..da97adff 100644 --- a/speechclient.hh +++ b/speechclient.hh @@ -2,6 +2,7 @@ #define __SPEECHCLIENT_HH_INCLUDED__ #include +#include "config.hh" class SpeechClient: public QObject { @@ -13,18 +14,28 @@ public: { QString id; QString name; + // Volume and rate may vary from 0 to 100 + int volume; + int rate; + Engine( Config::VoiceEngine const & e ) : + id( e.id ) + , name( e.name ) + , volume( e.volume ) + , rate( e.rate ) + {} }; typedef QList Engines; - SpeechClient( QString const & engineId, QObject * parent = 0L ); + SpeechClient( Config::VoiceEngine const & e, QObject * parent = 0L ); virtual ~SpeechClient(); static Engines availableEngines(); const Engine & engine() const; - bool tell( QString const & text ); - bool say( QString const & text ); + + bool tell( QString const & text, int volume = -1, int rate = -1 ); + bool say( QString const & text, int volume = -1, int rate = -1 ); signals: void started( bool ok ); diff --git a/speechclient_win.cc b/speechclient_win.cc index d202813c..5b5be4d0 100644 --- a/speechclient_win.cc +++ b/speechclient_win.cc @@ -7,10 +7,13 @@ struct SpeechClient::InternalData { - InternalData( QString const & engineId ): + InternalData( Config::VoiceEngine const & e ): waitingFinish( false ) + , engine( e ) + , oldVolume( -1 ) + , oldRate( -1 ) { - sp = speechCreate( engineId.toStdWString().c_str() ); + sp = speechCreate( e.id.toStdWString().c_str() ); } ~InternalData() @@ -19,13 +22,15 @@ struct SpeechClient::InternalData } SpeechHelper sp; - Engine engine; bool waitingFinish; + Engine engine; + int oldVolume; + int oldRate; }; -SpeechClient::SpeechClient( QString const & engineId, QObject * parent ): +SpeechClient::SpeechClient( Config::VoiceEngine const & e, QObject * parent ): QObject( parent ), - internalData( new InternalData( engineId ) ) + internalData( new InternalData( e ) ) { } @@ -40,11 +45,10 @@ static bool enumEngines( void * /* token */, void * userData ) { SpeechClient::Engines * pEngines = ( SpeechClient::Engines * )userData; - SpeechClient::Engine engine = - { + SpeechClient::Engine engine( Config::VoiceEngine( QString::fromWCharArray( id ), - QString::fromWCharArray( name ) - }; + QString::fromWCharArray( name ), + 50, 50 ) ); pEngines->push_back( engine ); return true; } @@ -61,7 +65,7 @@ const SpeechClient::Engine & SpeechClient::engine() const return internalData->engine; } -bool SpeechClient::tell( QString const & text ) +bool SpeechClient::tell( QString const & text, int volume, int rate ) { if ( !speechAvailable( internalData->sp ) ) return false; @@ -69,7 +73,16 @@ bool SpeechClient::tell( QString const & text ) if ( internalData->waitingFinish ) return false; + if( volume < 0 ) + volume = engine().volume; + if( rate < 0 ) + rate = engine().rate; + + internalData->oldVolume = setSpeechVolume( internalData->sp, volume ); + internalData->oldRate = setSpeechRate( internalData->sp, rate ); + bool ok = speechTell( internalData->sp, text.toStdWString().c_str() ); + emit started( ok ); if ( ok ) @@ -84,12 +97,27 @@ bool SpeechClient::tell( QString const & text ) return ok; } -bool SpeechClient::say( QString const & text ) +bool SpeechClient::say( QString const & text, int volume, int rate ) { if ( !speechAvailable( internalData->sp ) ) return false; - return speechSay( internalData->sp, text.toStdWString().c_str() ); + if( volume < 0 ) + volume = engine().volume; + if( rate < 0 ) + rate = engine().rate; + + int oldVolume = setSpeechVolume( internalData->sp, volume ); + int oldRate = setSpeechRate( internalData->sp, rate ); + + bool ok = speechSay( internalData->sp, text.toStdWString().c_str() ); + + if( oldVolume >=0 ) + setSpeechVolume( internalData->sp, oldVolume ); + if( oldRate >=0 ) + setSpeechRate( internalData->sp, oldRate ); + + return ok; } void SpeechClient::timerEvent( QTimerEvent * evt ) @@ -103,6 +131,14 @@ void SpeechClient::timerEvent( QTimerEvent * evt ) { killTimer( evt->timerId() ) ; internalData->waitingFinish = false; + + if( internalData->oldVolume >=0 ) + setSpeechVolume( internalData->sp, internalData->oldVolume ); + if( internalData->oldRate >=0 ) + setSpeechRate( internalData->sp, internalData->oldRate ); + internalData->oldVolume = -1; + internalData->oldRate = -1; + emit finished(); } } diff --git a/speechhlp.cc b/speechhlp.cc index c4a52dba..0795b182 100644 --- a/speechhlp.cc +++ b/speechhlp.cc @@ -165,3 +165,27 @@ bool speechSay(SpeechHelper sp, const wchar_t *text) HRESULT hr = sp->voice->Speak(text, SPF_IS_NOT_XML, 0); return !!SUCCEEDED(hr); } + +int setSpeechVolume( SpeechHelper sp, int newVolume ) +{ + if( !sp || !sp->voice || newVolume < 0 || newVolume > 100 ) + return -1; + unsigned short oldVolume; + HRESULT hr = sp->voice->GetVolume( &oldVolume ); + if( !SUCCEEDED( hr ) ) + return -1; + sp->voice->SetVolume( (unsigned short) newVolume ); + return oldVolume; +} + +int setSpeechRate( SpeechHelper sp, int newRate ) +{ + if( !sp || !sp->voice || newRate < 0 || newRate > 100 ) + return -1; + long oldRate; + HRESULT hr = sp->voice->GetRate( &oldRate ); + if( !SUCCEEDED( hr ) ) + return -1; + sp->voice->SetRate( ( newRate - 50 ) / 5 ); + return oldRate * 5 + 50; +} diff --git a/speechhlp.hh b/speechhlp.hh index 98a29454..2e7eed54 100644 --- a/speechhlp.hh +++ b/speechhlp.hh @@ -17,6 +17,8 @@ const wchar_t * speechEngineName(SpeechHelper sp); bool speechTell(SpeechHelper sp, const wchar_t *text); bool speechTellFinished(SpeechHelper sp); bool speechSay(SpeechHelper sp, const wchar_t *text); +int setSpeechVolume( SpeechHelper sp, int newVolume ); +int setSpeechRate( SpeechHelper sp, int newRate ); #ifdef __cplusplus } diff --git a/sphelper.hh b/sphelper.hh index 0588eaed..04b9b314 100644 --- a/sphelper.hh +++ b/sphelper.hh @@ -17,6 +17,11 @@ #define SPERR_NOT_FOUND MAKE_SAPI_ERROR(0x03a) #endif +#ifdef _SAPI_VER +#undef _SAPI_VER +#endif +#define _SAPI_VER 0503 + inline void SpHexFromUlong(WCHAR * psz, ULONG ul) { // If for some reason we cannot convert a number, set it to 0 diff --git a/texttospeechsource.cc b/texttospeechsource.cc index 94fecded..28ba746d 100644 --- a/texttospeechsource.cc +++ b/texttospeechsource.cc @@ -25,6 +25,30 @@ TextToSpeechSource::TextToSpeechSource( QWidget * parent, { ui.availableVoiceEngines->addItem( engine.name, engine.id ); } + + if( voiceEngines.count() > 0 ) + { + QModelIndex const &idx = ui.selectedVoiceEngines->model()->index( 0, 0 ); + if( idx.isValid() ) + ui.selectedVoiceEngines->setCurrentIndex( idx ); + } + + adjustSliders(); + + connect( ui.volumeSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( slidersChanged() ) ); + connect( ui.rateSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( slidersChanged() ) ); + connect( ui.selectedVoiceEngines->selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), + this, SLOT( selectionChanged() ) ); +} + +void TextToSpeechSource::slidersChanged() +{ + if( ui.selectedVoiceEngines->selectionModel()->hasSelection() ) + voiceEnginesModel.setEngineParams( ui.selectedVoiceEngines->currentIndex(), + ui.volumeSlider->value(), + ui.rateSlider->value()) ; } void TextToSpeechSource::on_addVoiceEngine_clicked() @@ -37,11 +61,14 @@ void TextToSpeechSource::on_addVoiceEngine_clicked() return; } - // Fake id and name - QString name = ui.availableVoiceEngines->itemText( 0 ); - QString id = ui.availableVoiceEngines->itemData( 0 ).toString(); - voiceEnginesModel.addNewVoiceEngine( id, name ); - fitSelectedVoiceEnginesColumns(); + int idx = ui.availableVoiceEngines->currentIndex(); + if( idx >= 0 ) + { + QString name = ui.availableVoiceEngines->itemText( idx ); + QString id = ui.availableVoiceEngines->itemData( idx ).toString(); + voiceEnginesModel.addNewVoiceEngine( id, name, ui.volumeSlider->value(), ui.rateSlider->value() ); + fitSelectedVoiceEnginesColumns(); + } } void TextToSpeechSource::on_removeVoiceEngine_clicked() @@ -66,8 +93,12 @@ void TextToSpeechSource::on_previewVoice_clicked() return; QString engineId = ui.availableVoiceEngines->itemData( idx ).toString(); + QString name = ui.availableVoiceEngines->itemText( idx ); QString text = ui.previewText->text(); - SpeechClient * speechClient = new SpeechClient( engineId, this ); + int volume = ui.volumeSlider->value(); + int rate = ui.rateSlider->value(); + + SpeechClient * speechClient = new SpeechClient( Config::VoiceEngine( engineId, name, volume, rate ), this ); connect( speechClient, SIGNAL( started( bool ) ), ui.previewVoice, SLOT( setDisabled( bool ) ) ); connect( speechClient, SIGNAL( finished() ), this, SLOT( previewVoiceFinished() ) ); @@ -87,6 +118,35 @@ void TextToSpeechSource::fitSelectedVoiceEnginesColumns() ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnIcon ); } +void TextToSpeechSource::adjustSliders() +{ + QModelIndex const & index = ui.selectedVoiceEngines->currentIndex(); + if ( index.isValid() ) + { + Config::VoiceEngines const &engines = voiceEnginesModel.getCurrentVoiceEngines(); + ui.volumeSlider->setValue( engines[ index.row() ].volume ); + ui.rateSlider->setValue( engines[ index.row() ].rate ); + return; + } + ui.volumeSlider->setValue( 50 ); + ui.rateSlider->setValue( 50 ); +} + +void TextToSpeechSource::selectionChanged() +{ + disconnect( ui.volumeSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( slidersChanged() ) ); + disconnect( ui.rateSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( slidersChanged() ) ); + + adjustSliders(); + + connect( ui.volumeSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( slidersChanged() ) ); + connect( ui.rateSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( slidersChanged() ) ); +} + VoiceEnginesModel::VoiceEnginesModel( QWidget * parent, Config::VoiceEngines const & voiceEngines ): QAbstractItemModel( parent ), voiceEngines( voiceEngines ) @@ -100,7 +160,8 @@ void VoiceEnginesModel::removeVoiceEngine( int index ) endRemoveRows(); } -void VoiceEnginesModel::addNewVoiceEngine( QString const & id, QString const & name ) +void VoiceEnginesModel::addNewVoiceEngine( QString const & id, QString const & name, + int volume, int rate ) { if ( id.isEmpty() || name.isEmpty() ) return; @@ -109,6 +170,8 @@ void VoiceEnginesModel::addNewVoiceEngine( QString const & id, QString const & n v.enabled = true; v.id = id; v.name = name; + v.volume = volume; + v.rate = rate; beginInsertRows( QModelIndex(), voiceEngines.size(), voiceEngines.size() ); voiceEngines.push_back( v ); @@ -243,6 +306,15 @@ bool VoiceEnginesModel::setData( QModelIndex const & index, const QVariant & val return false; } +void VoiceEnginesModel::setEngineParams( QModelIndex idx, int volume, int rate ) +{ + if ( idx.isValid() ) + { + voiceEngines[ idx.row() ].volume = volume; + voiceEngines[ idx.row() ].rate = rate; + } +} + VoiceEngineEditor::VoiceEngineEditor( SpeechClient::Engines const & engines, QWidget * parent ): QComboBox( parent ) { diff --git a/texttospeechsource.hh b/texttospeechsource.hh index 42eaf8e2..aea4fdc6 100644 --- a/texttospeechsource.hh +++ b/texttospeechsource.hh @@ -29,10 +29,12 @@ public: VoiceEnginesModel( QWidget * parent, Config::VoiceEngines const & voiceEngines ); void removeVoiceEngine( int index ); - void addNewVoiceEngine( QString const & id, QString const & name ); + void addNewVoiceEngine( QString const & id, QString const & name, + int volume, int rate ); Config::VoiceEngines const & getCurrentVoiceEngines() const { return voiceEngines; } + void setEngineParams( QModelIndex idx, int volume, int rate ); QModelIndex index( int row, int column, QModelIndex const & parent ) const; QModelIndex parent( QModelIndex const & parent ) const; @@ -94,12 +96,15 @@ private slots: void on_removeVoiceEngine_clicked(); void on_previewVoice_clicked(); void previewVoiceFinished(); + void slidersChanged(); + void selectionChanged(); private: Ui::TextToSpeechSource ui; VoiceEnginesModel voiceEnginesModel; void fitSelectedVoiceEnginesColumns(); + void adjustSliders(); }; #endif // __TEXTTOSPEECHSOURCE_HH_INCLUDED__ diff --git a/texttospeechsource.ui b/texttospeechsource.ui index aedc40b3..06d381c4 100644 --- a/texttospeechsource.ui +++ b/texttospeechsource.ui @@ -78,6 +78,73 @@ + + + + Preferences + + + + + + Volume: + + + + + + + 100 + + + 50 + + + false + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + Rate: + + + + + + + 100 + + + 50 + + + false + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + +