Win-specific: Add volume and rate tuning for TTS, fix some errors

This commit is contained in:
Abs62 2013-04-26 17:41:39 +04:00
parent 58654a7423
commit 0fb8eed553
12 changed files with 288 additions and 39 deletions

View file

@ -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;

View file

@ -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 );
}
}

View file

@ -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;

View file

@ -348,7 +348,8 @@ win32 {
texttospeechsource.hh \
sapi.hh \
sphelper.hh \
speechclient.hh
speechclient.hh \
speechhlp.hh
}
mac {

View file

@ -2,6 +2,7 @@
#define __SPEECHCLIENT_HH_INCLUDED__
#include <QObject>
#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<Engine> 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 );

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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
}

View file

@ -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

View file

@ -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 )
{

View file

@ -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__

View file

@ -78,6 +78,73 @@
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Preferences</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Volume:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="volumeSlider">
<property name="maximum">
<number>100</number>
</property>
<property name="sliderPosition">
<number>50</number>
</property>
<property name="tracking">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Rate:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rateSlider">
<property name="maximum">
<number>100</number>
</property>
<property name="sliderPosition">
<number>50</number>
</property>
<property name="tracking">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">