From 870b4d7f9ca5b2d69e416ed5f3a75c88073d4c5f Mon Sep 17 00:00:00 2001 From: James Teh Date: Tue, 2 May 2023 21:43:55 +1000 Subject: [PATCH 1/6] Add setting to have the volume of NVDA sounds follow the voice volume. --- source/config/configSpec.py | 1 + source/gui/settingsDialogs.py | 15 +++++++++++++++ source/nvwave.py | 14 ++++++++++++++ user_docs/en/userGuide.t2t | 7 +++++++ 4 files changed, 37 insertions(+) diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 2ea4f4d9d86..2a233998e5d 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -58,6 +58,7 @@ [audio] audioDuckingMode = integer(default=0) wasapi = boolean(default=true) + soundVolumeFollowsVoice = boolean(default=false) # Braille settings [braille] diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 2b477a103c6..52c505bb601 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -3067,6 +3067,18 @@ def __init__(self, parent): ) self.wasapiCheckBox.defaultValue = self._getDefaultValue( ["audio", "wasapi"]) + # Translators: This is the label for a checkbox control in the + # Advanced settings panel. + label = _("Volume of NVDA sounds follows voice volume (requires WASAPI)") + self.soundVolFollowCheckBox: wx.CheckBox = audioGroup.addItem( + wx.CheckBox(audioBox, label=label) + ) + self.bindHelpEvent("SoundVolumeFollowsVoice", self.soundVolFollowCheckBox) + self.soundVolFollowCheckBox.SetValue( + config.conf["audio"]["soundVolumeFollowsVoice"] + ) + self.soundVolFollowCheckBox.defaultValue = self._getDefaultValue( + ["audio", "soundVolumeFollowsVoice"]) # Translators: This is the label for a group of advanced options in the # Advanced settings panel @@ -3156,6 +3168,7 @@ def haveConfigDefaultsBeenRestored(self): and self.caretMoveTimeoutSpinControl.GetValue() == self.caretMoveTimeoutSpinControl.defaultValue and self.reportTransparentColorCheckBox.GetValue() == self.reportTransparentColorCheckBox.defaultValue and self.wasapiCheckBox.GetValue() == self.wasapiCheckBox.defaultValue + and self.soundVolFollowCheckBox.GetValue() == self.soundVolFollowCheckBox.defaultValue and set(self.logCategoriesList.CheckedItems) == set(self.logCategoriesList.defaultCheckedItems) and self.playErrorSoundCombo.GetSelection() == self.playErrorSoundCombo.defaultValue and True # reduce noise in diff when the list is extended. @@ -3182,6 +3195,7 @@ def restoreToDefaults(self): self.caretMoveTimeoutSpinControl.SetValue(self.caretMoveTimeoutSpinControl.defaultValue) self.reportTransparentColorCheckBox.SetValue(self.reportTransparentColorCheckBox.defaultValue) self.wasapiCheckBox.SetValue(self.wasapiCheckBox.defaultValue) + self.soundVolFollowCheckBox.SetValue(self.soundVolFollowCheckBox.defaultValue) self.logCategoriesList.CheckedItems = self.logCategoriesList.defaultCheckedItems self.playErrorSoundCombo.SetSelection(self.playErrorSoundCombo.defaultValue) self._defaultsRestored = True @@ -3213,6 +3227,7 @@ def onSave(self): self.reportTransparentColorCheckBox.IsChecked() ) config.conf["audio"]["wasapi"] = self.wasapiCheckBox.IsChecked() + config.conf["audio"]["soundVolumeFollowsVoice"] = self.soundVolFollowCheckBox.IsChecked() config.conf["annotations"]["reportDetails"] = self.annotationsDetailsCheckBox.IsChecked() config.conf["annotations"]["reportAriaDescription"] = self.ariaDescCheckBox.IsChecked() config.conf["braille"]["enableHidBrailleSupport"] = self.supportHidBrailleCombo.GetSelection() diff --git a/source/nvwave.py b/source/nvwave.py index 61c3ce8ed73..58ac1914db5 100644 --- a/source/nvwave.py +++ b/source/nvwave.py @@ -814,6 +814,7 @@ def __init__( import audioDucking if audioDucking.isAudioDuckingSupported(): self._audioDucker = audioDucking.AudioDucker() + self._session = session self._player = NVDAHelper.localLib.wasPlay_create( self._deviceNameToId(outputDevice), format, @@ -857,6 +858,7 @@ def open(self): WavePlayer.audioDeviceError_static = True raise WasapiWavePlayer.audioDeviceError_static = False + self._sessionVolumeFollow() def close(self): """For WASAPI, this just stops playback. @@ -913,6 +915,7 @@ def stop(self): self._audioDucker.disable() NVDAHelper.localLib.wasPlay_stop(self._player) self._doneCallbacks = {} + self._sessionVolumeFollow() def pause(self, switch: bool): """Pause or unpause playback. @@ -935,6 +938,17 @@ def setSessionVolume(self, level: float): """ NVDAHelper.localLib.wasPlay_setSessionVolume(self._player, c_float(level)) + def _sessionVolumeFollow(self): + if ( + self._session is not soundsSession + or not config.conf["audio"]["soundVolumeFollowsVoice"] + ): + return + import synthDriverHandler + synth = synthDriverHandler.getSynth() + if synth and synth.isSupported("volume"): + self.setSessionVolume(synth.volume / 100) + @staticmethod def _getDevices(): rawDevs = BSTR() diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 583b0d52d21..4d440e2f9ba 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -2324,6 +2324,13 @@ WASAPI is a more modern audio framework which may improve the responsiveness, pe This option is enabled by default. After changing this option, you will need to restart NVDA for the change to take effect. +==== Volume of NVDA sounds follows voice volume ====[SoundVolumeFollowsVoice] +When this option is enabled, the volume of NVDA sounds and beeps will follow the volume setting of the voice you are using. +If you decrease the volume of the voice, the volume of sounds will decrease. +Similarly, if you increase the volume of the voice, the volume of sounds will increase. +This option only takes effect when "Use WASAPI for audio output" is enabled. +This option is disabled by default. + ==== Debug logging categories ====[AdvancedSettingsDebugLoggingCategories] The checkboxes in this list allow you to enable specific categories of debug messages in NVDA's log. Logging these messages can result in decreased performance and large log files. From 6ea4d8d663050066edaccc3bc44e1a85e073aaba Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 3 May 2023 19:39:40 +1000 Subject: [PATCH 2/6] When soundVolumeFollowsVoice gets disabled, reset the sound volume to maximum. --- source/nvwave.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source/nvwave.py b/source/nvwave.py index 58ac1914db5..c4cd4fd2836 100644 --- a/source/nvwave.py +++ b/source/nvwave.py @@ -774,6 +774,10 @@ class WasapiWavePlayer(garbageHandler.TrackedObject): #: This allows us to have a single callback in the class rather than on #: each instance, which prevents reference cycles. _instances = weakref.WeakValueDictionary() + #: The previous value of the soundVolumeFollowsVoice setting. This is used to + #: determine when this setting has been disabled when it was previously + #: enabled. + _prevSoundVolFollow: bool = False def __init__( self, @@ -939,10 +943,14 @@ def setSessionVolume(self, level: float): NVDAHelper.localLib.wasPlay_setSessionVolume(self._player, c_float(level)) def _sessionVolumeFollow(self): - if ( - self._session is not soundsSession - or not config.conf["audio"]["soundVolumeFollowsVoice"] - ): + if self._session is not soundsSession: + return + follow = config.conf["audio"]["soundVolumeFollowsVoice"] + if not follow and WavePlayer._prevSoundVolFollow: + # Following was disabled. Reset the sound volume to maximum. + self.setSessionVolume(1.0) + WavePlayer._prevSoundVolFollow = follow + if not follow: return import synthDriverHandler synth = synthDriverHandler.getSynth() From 109e663fce26202f13ff1459b8b4085c51e61b29 Mon Sep 17 00:00:00 2001 From: James Teh Date: Thu, 4 May 2023 14:21:19 +1000 Subject: [PATCH 3/6] Don't fail to set the session volume if the current device is disconnected. --- nvdaHelper/local/wasapi.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nvdaHelper/local/wasapi.cpp b/nvdaHelper/local/wasapi.cpp index 9e5c78f4a8b..db08351dab4 100644 --- a/nvdaHelper/local/wasapi.cpp +++ b/nvdaHelper/local/wasapi.cpp @@ -513,6 +513,16 @@ HRESULT WasapiPlayer::resume() { HRESULT WasapiPlayer::setSessionVolume(float level) { CComPtr volume; HRESULT hr = client->GetService(IID_ISimpleAudioVolume, (void**)&volume); + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { + // If we're using a specific device, it's just been invalidated. Fall back + // to the default device. + deviceId.clear(); + hr = open(true); + if (FAILED(hr)) { + return hr; + } + hr = client->GetService(IID_ISimpleAudioVolume, (void**)&volume); + } if (FAILED(hr)) { return hr; } From d6bfdb81b8603e958b9a6cb0c3ffb8cf7b543689 Mon Sep 17 00:00:00 2001 From: James Teh Date: Sat, 10 Jun 2023 14:14:51 +1000 Subject: [PATCH 4/6] Disable the "Volume of NVDA sounds follows voice volume" checkbox if the WASAPI checkbox is unchecked. --- source/gui/settingsDialogs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 52c505bb601..b0437778fba 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -3062,6 +3062,7 @@ def __init__(self, parent): wx.CheckBox(audioBox, label=label) ) self.bindHelpEvent("WASAPI", self.wasapiCheckBox) + self.wasapiCheckBox.Bind(wx.EVT_CHECKBOX, self.onWasapiChange) self.wasapiCheckBox.SetValue( config.conf["audio"]["wasapi"] ) @@ -3079,6 +3080,8 @@ def __init__(self, parent): ) self.soundVolFollowCheckBox.defaultValue = self._getDefaultValue( ["audio", "soundVolumeFollowsVoice"]) + if not self.wasapiCheckBox.GetValue(): + self.soundVolFollowCheckBox.Disable() # Translators: This is the label for a group of advanced options in the # Advanced settings panel @@ -3141,6 +3144,9 @@ def onOpenScratchpadDir(self,evt): path=config.getScratchpadDir(ensureExists=True) os.startfile(path) + def onWasapiChange(self, evt): + self.soundVolFollowCheckBox.Enable(evt.IsChecked()) + def _getDefaultValue(self, configPath): return config.conf.getConfigValidation(configPath).default From 1a73975b007782b817f05762e8857715832ea489 Mon Sep 17 00:00:00 2001 From: James Teh Date: Tue, 13 Jun 2023 20:29:13 +1000 Subject: [PATCH 5/6] Annotate onWasapiChange argument. --- source/gui/settingsDialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index b0437778fba..8280e6d475d 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -3144,7 +3144,7 @@ def onOpenScratchpadDir(self,evt): path=config.getScratchpadDir(ensureExists=True) os.startfile(path) - def onWasapiChange(self, evt): + def onWasapiChange(self, evt: wx.CommandEvent): self.soundVolFollowCheckBox.Enable(evt.IsChecked()) def _getDefaultValue(self, configPath): From a38a4bf2b72c0ff6428db8fbbfa5668629160e62 Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Wed, 14 Jun 2023 11:24:59 +1000 Subject: [PATCH 6/6] update changes --- user_docs/en/changes.t2t | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index aac11f75595..377ff02ee94 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -31,10 +31,15 @@ There are now gestures for showing the braille settings dialog, accessing the st - When highlighted text is enabled Document Formatting, highlight colours are now reported in Microsoft Word. (#7396, #12101, #5866) - When colors are enabled Document Formatting, background colours are now reported in Microsoft Word. (#5866) - When pressing ``numpad2`` three times to report the numerical value of the character at the position of the review cursor, the information is now also provided in braille. (#14826) -- NVDA now outputs audio via the Windows Audio Session API (WASAPI), which may improve the responsiveness, performance and stability of NVDA speech and sounds. This can be disabled in Advanced settings if audio problems are encountered. (#14697) +- NVDA now outputs audio via the Windows Audio Session API (WASAPI), which may improve the responsiveness, performance and stability of NVDA speech and sounds. +This can be disabled in Advanced settings if audio problems are encountered. (#14697) - When using Excel shortcuts to toggle format such as bold, italic, underline and strike through of a cell in Excel, the result is now reported. (#14923) - Added support for the Help Tech Activator Braille display. (#14917) -- In Windows 10 May 2019 Update and later, NVDA can announe virtual desktop names when opening, changing, and closing them. (#5641) +- In Windows 10 May 2019 Update and later, NVDA can announce virtual desktop names when opening, changing, and closing them. (#5641) +- It is now possible to have the volume of NVDA sounds and beeps follow the volume setting of the voice you are using. +This option can be enabled in Advanced settings. (#1409) +- You can now separately control the volume of NVDA sounds. +This can be done using the Windows Volume Mixer. (#1409) -