Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting to have the volume of NVDA sounds follow the voice volume. #14896

Merged
merged 7 commits into from
Jun 14, 2023
Merged
10 changes: 10 additions & 0 deletions nvdaHelper/local/wasapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,16 @@ HRESULT WasapiPlayer::resume() {
HRESULT WasapiPlayer::setSessionVolume(float level) {
CComPtr<ISimpleAudioVolume> 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;
}
Expand Down
1 change: 1 addition & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
[audio]
audioDuckingMode = integer(default=0)
wasapi = boolean(default=true)
soundVolumeFollowsVoice = boolean(default=false)

# Braille settings
[braille]
Expand Down
21 changes: 21 additions & 0 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3062,11 +3062,26 @@ 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"]
)
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(
jcsteh marked this conversation as resolved.
Show resolved Hide resolved
["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
Expand Down Expand Up @@ -3129,6 +3144,9 @@ def onOpenScratchpadDir(self,evt):
path=config.getScratchpadDir(ensureExists=True)
os.startfile(path)

def onWasapiChange(self, evt: wx.CommandEvent):
self.soundVolFollowCheckBox.Enable(evt.IsChecked())
seanbudd marked this conversation as resolved.
Show resolved Hide resolved

def _getDefaultValue(self, configPath):
return config.conf.getConfigValidation(configPath).default

Expand Down Expand Up @@ -3156,6 +3174,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.
Expand All @@ -3182,6 +3201,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
Expand Down Expand Up @@ -3213,6 +3233,7 @@ def onSave(self):
self.reportTransparentColorCheckBox.IsChecked()
)
config.conf["audio"]["wasapi"] = self.wasapiCheckBox.IsChecked()
config.conf["audio"]["soundVolumeFollowsVoice"] = self.soundVolFollowCheckBox.IsChecked()
jcsteh marked this conversation as resolved.
Show resolved Hide resolved
config.conf["annotations"]["reportDetails"] = self.annotationsDetailsCheckBox.IsChecked()
config.conf["annotations"]["reportAriaDescription"] = self.ariaDescCheckBox.IsChecked()
config.conf["braille"]["enableHidBrailleSupport"] = self.supportHidBrailleCombo.GetSelection()
Expand Down
22 changes: 22 additions & 0 deletions source/nvwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -814,6 +818,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,
Expand Down Expand Up @@ -857,6 +862,7 @@ def open(self):
WavePlayer.audioDeviceError_static = True
raise
WasapiWavePlayer.audioDeviceError_static = False
self._sessionVolumeFollow()

def close(self):
"""For WASAPI, this just stops playback.
Expand Down Expand Up @@ -913,6 +919,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.
Expand All @@ -935,6 +942,21 @@ def setSessionVolume(self, level: float):
"""
NVDAHelper.localLib.wasPlay_setSessionVolume(self._player, c_float(level))

def _sessionVolumeFollow(self):
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()
if synth and synth.isSupported("volume"):
self.setSessionVolume(synth.volume / 100)

@staticmethod
def _getDevices():
rawDevs = BSTR()
Expand Down
7 changes: 7 additions & 0 deletions user_docs/en/userGuide.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down