From fd2b070bb15055cb791045c08a0e61fb525e7e4f Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 25 Aug 2026 11:09:09 -0400 Subject: [PATCH] FS#13984 - Rockbox Utility: Add SAPI5 voice volume control (Alessio Lenzi) Rockbox Utility currently exposes SAPI5 voice speed but not the SAPI voice volume. The encoder volume setting is applied after synthesis, so it cannot prevent clipping or distortion already present in the generated wave file. This patch adds a Volume control to the SAPI5 TTS configuration. The value is passed to the standard SpVoice.Volume property before synthesis. Change-Id: Ifa43e55f716f98150e94c86e5e6a6c8bce0ca852 --- tools/sapi_voice.vbs | 5 ++++- utils/rbutilqt/base/rbsettings.cpp | 1 + utils/rbutilqt/base/rbsettings.h | 1 + utils/rbutilqt/base/ttssapi.cpp | 11 +++++++++++ utils/rbutilqt/base/ttssapi.h | 2 ++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/sapi_voice.vbs b/tools/sapi_voice.vbs index 5bfc820dfa..0242b812f8 100644 --- a/tools/sapi_voice.vbs +++ b/tools/sapi_voice.vbs @@ -39,7 +39,7 @@ Dim oShell, oArgs, oEnv Dim oFSO, oStdIn, oStdOut Dim bVerbose, bList Dim bMSSP -Dim sLanguage, sVoice, sSpeed, sName, sVendor +Dim sLanguage, sVoice, sSpeed, sVolume, sName, sVendor Dim oSpVoice, oSpFS ' SAPI5 voice and filestream Dim oVoice ' for traversing the list of voices @@ -64,6 +64,7 @@ bList = oArgs.Exists("listvoices") sLanguage = oArgs.Item("language") sVoice = oArgs.Item("voice") sSpeed = oArgs.Item("speed") +sVolume = oArgs.Item("volume") ' Create SAPI5 object If bMSSP Then @@ -116,6 +117,8 @@ End If ' Speed selection If sSpeed <> "" Then oSpVoice.Rate = sSpeed +' Volume selection +If sVolume <> "" Then oSpVoice.Volume = sVolume ' Get vendor information, protect from missing attribute sVendor = oSpVoice.Voice.GetAttribute("Vendor") If Err.Number <> 0 Then diff --git a/utils/rbutilqt/base/rbsettings.cpp b/utils/rbutilqt/base/rbsettings.cpp index 4e618ea01c..6cb1353831 100644 --- a/utils/rbutilqt/base/rbsettings.cpp +++ b/utils/rbutilqt/base/rbsettings.cpp @@ -77,6 +77,7 @@ const static struct { { RbSettings::EncoderNarrowBand, ":encoder:/narrowband", "false" }, { RbSettings::WavtrimThreshold, "wavtrimthreshold", "500"}, { RbSettings::TtsSpeed, ":tts:/speed", "175" }, + { RbSettings::TtsVolume, ":tts:/volume", "100" }, { RbSettings::EncoderComplexity, ":encoder:/complexity", "10" }, { RbSettings::EncoderQuality, ":encoder:/quality", "-1.0" }, { RbSettings::EncoderVolume, ":encoder:/volume", "1.0" }, diff --git a/utils/rbutilqt/base/rbsettings.h b/utils/rbutilqt/base/rbsettings.h index a94486da20..cd525d5d5e 100644 --- a/utils/rbutilqt/base/rbsettings.h +++ b/utils/rbutilqt/base/rbsettings.h @@ -69,6 +69,7 @@ class RbSettings : public QObject WavtrimThreshold, EncoderComplexity, TtsSpeed, + TtsVolume, CacheDisabled, EncoderNarrowBand, EncoderQuality, diff --git a/utils/rbutilqt/base/ttssapi.cpp b/utils/rbutilqt/base/ttssapi.cpp index 1ef12cb628..0a612213fa 100644 --- a/utils/rbutilqt/base/ttssapi.cpp +++ b/utils/rbutilqt/base/ttssapi.cpp @@ -29,6 +29,7 @@ TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) m_TTSTemplate << "/language:%lang"; m_TTSTemplate << "/voice:%voice"; m_TTSTemplate << "/speed:%speed"; + m_TTSTemplate << "/volume:%volume"; m_TTSTemplate << "%options"; m_TTSVoiceTemplate << "//nologo"; @@ -74,6 +75,12 @@ void TTSSapi::generateSettings() speed = 0; insertSetting(eSPEED, new EncTtsSetting(this, EncTtsSetting::eINT, tr("Speed:"), speed, -10, 10)); + // volume + int volume = RbSettings::subValue(m_TTSType, RbSettings::TtsVolume).toInt(); + if(volume > 100 || volume < 0) + volume = 100; + insertSetting(eVOLUME, new EncTtsSetting(this, + EncTtsSetting::eINT, tr("Volume:"), volume, 0, 100)); // options insertSetting(eOPTIONS, new EncTtsSetting(this, EncTtsSetting::eSTRING, tr("Options:"), @@ -90,6 +97,8 @@ void TTSSapi::saveSettings() getSetting(eVOICE)->current().toString()); RbSettings::setSubValue(m_TTSType, RbSettings::TtsSpeed, getSetting(eSPEED)->current().toInt()); + RbSettings::setSubValue(m_TTSType, RbSettings::TtsVolume, + getSetting(eVOLUME)->current().toInt()); RbSettings::setSubValue(m_TTSType, RbSettings::TtsOptions, getSetting(eOPTIONS)->current().toString()); @@ -112,6 +121,7 @@ bool TTSSapi::start(QString *errStr) m_TTSLanguage =RbSettings::subValue(m_TTSType, RbSettings::TtsLanguage).toString(); m_TTSVoice=RbSettings::subValue(m_TTSType, RbSettings::TtsVoice).toString(); m_TTSSpeed=RbSettings::subValue(m_TTSType, RbSettings::TtsSpeed).toString(); + m_TTSVolume=RbSettings::subValue(m_TTSType, RbSettings::TtsVolume).toString(); QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); @@ -130,6 +140,7 @@ bool TTSSapi::start(QString *errStr) exec.replaceInStrings("%lang",m_TTSLanguage); exec.replaceInStrings("%voice",m_TTSVoice); exec.replaceInStrings("%speed",m_TTSSpeed); + exec.replaceInStrings("%volume",m_TTSVolume); LOG_INFO() << "Start: cscript " << exec; voicescript = new QProcess(nullptr); diff --git a/utils/rbutilqt/base/ttssapi.h b/utils/rbutilqt/base/ttssapi.h index 8ea1370133..4aa8f25c4c 100644 --- a/utils/rbutilqt/base/ttssapi.h +++ b/utils/rbutilqt/base/ttssapi.h @@ -31,6 +31,7 @@ class TTSSapi : public TTSBase eLANGUAGE, eVOICE, eSPEED, + eVOLUME, eOPTIONS }; @@ -64,6 +65,7 @@ class TTSSapi : public TTSBase QString m_TTSLanguage; QString m_TTSVoice; QString m_TTSSpeed; + QString m_TTSVolume; bool m_started; protected: