Skip to content

Add possibility to cycle through output devices forward and backward#15493

Closed
beqabeqa473 wants to merge 3 commits into
nvaccess:masterfrom
beqabeqa473:CycleOutputDevices
Closed

Add possibility to cycle through output devices forward and backward#15493
beqabeqa473 wants to merge 3 commits into
nvaccess:masterfrom
beqabeqa473:CycleOutputDevices

Conversation

@beqabeqa473

@beqabeqa473 beqabeqa473 commented Sep 21, 2023

Copy link
Copy Markdown
Contributor

Link to issue number:

Fixes #8801

Summary of the issue:

After introducing an additional audio panel and moving output device selection out of synthesizer selection dialog, it became harder to recover from situation where audio device is loosing and speech doesn't output anymore.
It is now possible to quickly switch to the next or previous output devices with shortcuts

Description of user facing changes

Added two unassigned shortcuts to switch to the next and previous output device.

Description of development approach

Added two scripts and a helper method to globalCommands.py
moved out setting output device from gui.settingDialogs to a dedicated utils.synth module to eliminate code duplication

Testing strategy:

Tried to assign shortcuts to scripts listed above and confirmed that they switch output device forward and backward.

Known issues with pull request:

None found

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

@AppVeyorBot

Copy link
Copy Markdown

See test results for failed build of commit af326aebcd

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Is there a particular reason why we need two scripts for this? Shoudln't this just be a single cycler?

@beqabeqa473

beqabeqa473 commented Sep 21, 2023 via email

Copy link
Copy Markdown
Contributor Author

@XLTechie

XLTechie commented Sep 21, 2023 via email

Copy link
Copy Markdown
Collaborator

@AppVeyorBot

Copy link
Copy Markdown

See test results for failed build of commit 621b8a74e5

@beqabeqa473 beqabeqa473 marked this pull request as ready for review September 21, 2023 15:53
@beqabeqa473 beqabeqa473 requested review from a team as code owners September 21, 2023 15:53
@beqabeqa473 beqabeqa473 marked this pull request as draft September 21, 2023 16:09
@seanbudd

Copy link
Copy Markdown
Member

Is this ready for review?

@beqabeqa473

beqabeqa473 commented Sep 22, 2023 via email

Copy link
Copy Markdown
Contributor Author

Comment thread source/globalCommands.py Outdated
from base64 import b16encode
import vision
from utils.security import objectBelowLockScreenAndWindowsIsLocked
from utils.synth import getOutputDeviceNames, setOutputDevice

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly puzzled why this module is called synth while in fact, it contains logic for audio. I'm also not sure why this can't just reside in the nvwave module.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, it doesn't contain logic for audio. Switching to another
audio device involves reinitializing tts engine and tones module.
I wanted to eliminate code duplication and decoupled this code.
If you have a better alternative i am glad to implement it.

Both of these functions are related to audio, not synthesizers.
I would rename the utils.synth module to audio or move to nvwave like @LeonarddeR suggests

@beqabeqa473

beqabeqa473 commented Sep 22, 2023 via email

Copy link
Copy Markdown
Contributor Author

@seanbudd seanbudd added this to the 2024.1 milestone Sep 26, 2023
@seanbudd seanbudd added the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Oct 16, 2023
@seanbudd seanbudd removed this from the 2024.1 milestone Oct 30, 2023
Comment thread source/globalCommands.py Outdated
config.conf["documentNavigation"]["paragraphStyle"] = newFlag.name
ui.message(newFlag.displayString)

def cycleOutputAudioDevices(self, direction: int):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be made private (underscore prefixed), and converted to a static method, as far as I can tel it does not need access to the instance. Also the type annotation should reflect the fact that direction cannot be an arbitrary int, but only 1, or -1, I'd suggest using typing.Literal here.

@XLTechie XLTechie Nov 10, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using an int for direction, why not make it default to forward? Then you could use True/False, with a bool type defaulting to True.
Rename it from direction: int to cycleForward: bool = True or similar.
Then you wouldn't have to worry about int bounds violations, and the type checker could handle all of your call restriction logic.

Comment thread source/globalCommands.py Outdated
),
category=SCRCAT_SPEECH,
)
def script_switchToNextOutputDevice(self, gesture: "inputCore.InputGesture") -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No point in stringizing type hint here, same for the script below.

Comment thread source/utils/synth.py Outdated
import tones


def getOutputDeviceNames():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest this function should be moved to nvwave, with an appropriate name so that it is clear it returns friendly device names.

Comment thread user_docs/en/changes.t2t Outdated

== New Features ==
- Added support for Bluetooth Low Energy HID Braille displays. (#15470)
- Added unassigned gestures to to quickly cycle through output devices forward and backward. (#8801)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double 'to':

Suggested change
- Added unassigned gestures to to quickly cycle through output devices forward and backward. (#8801)
- Added unassigned gestures to quickly cycle through output devices forward and backward. (#8801)

Comment thread user_docs/en/userGuide.t2t Outdated

==== Output device ====[SelectSynthesizerOutputDevice]
This option allows you to choose the audio device that NVDA should instruct the selected synthesizer to speak through.
To switch to the next or previous output device, pleas assign custom shortcuts from [Input Gestures dialog #InputGestures].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pleas -> please

Comment thread source/utils/synth.py Outdated
def setOutputDevice(device: str):
config.conf["speech"]["outputDevice"] = device
currentSynth = getSynth()
if not setSynth(currentSynth.name):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree with the logic here. I understand you haven't written this code, but since we're revisiting it anyway let's try to make it nicer. Currently we set the configured device before checking if it can be used i.e. if the current synthesizer can speak through it. Also with the current design there is an import cycle, since you import gui here, yet gui imports from this new module. I'd move this to nvwave, but rewrite the code (in my view basic module for playing sound should not depend on other modules tones, synthesizer drivers, which depend on it), using extensionPoints.Decider in nvwave to which both tones and synthDriverHandler can subscribe (something like onMainAudioDeviceChanged). If both synthesizer and tones can work with the new device, only then it should be set in the config. Also in that case you can check what has been returned from the decider in the settings dialog, and display error in the gui there, and announce the new device, or failure in switching into it in the keyboard scripts. With this approach it would be necessary to modify tones and setSynth to accept the specified audio device, as until now they were always using the default one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi.

Could you please elaborate more on this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could implement decider but could you tell me, how i can understand that synthDriver or tones have succeeded or failed if i won't set config entry first?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have looked at the code more closely and while I still believe that setting device in the config before reinitializing synthesizer is not ideal, changing this will require a huge refactoring. Doing this as part of this PR is probably unjustified, so I'm fine with the current approach.

@beqabeqa473

beqabeqa473 commented Nov 9, 2023 via email

Copy link
Copy Markdown
Contributor Author

@cary-rowen

Copy link
Copy Markdown
Contributor

Hi @beqabeqa473
Would you consider having this ready for 2024.1?
Thanks

@XLTechie

Copy link
Copy Markdown
Collaborator

@beqabeqa473 What is the status of this? I would love to see it go to beta. I am in need of this feature. I'm going to have to implement it in an add-on if this PR is not proceeding soon.
Life gets in the way, I very much understand, but please mention it if you need help bringing this forward.

@beqabeqa473

beqabeqa473 commented Feb 16, 2024 via email

Copy link
Copy Markdown
Contributor Author

@beqabeqa473

Copy link
Copy Markdown
Contributor Author

@XLTechie hi, i pushed latest changes, and i think it is ready to review.

@beqabeqa473 beqabeqa473 marked this pull request as ready for review February 17, 2024 12:08
@AppVeyorBot

Copy link
Copy Markdown
  • FAIL: Translation comments check. Translation comments missing or unexpectedly included. See build log for more information.
  • PASS: Unit tests.
  • FAIL: Lint check. See test results for more information.
  • PASS: System tests (tags: installer NVDA).
  • Build (for testing PR): https://ci.appveyor.com/api/buildjobs/5w12s7p01fkrovfx/artifacts/output/nvda_snapshot_pr15493-31067,10ea7d96.exe
  • CI timing (mins):
    INIT 0.0,
    INSTALL_START 1.0,
    INSTALL_END 0.9,
    BUILD_START 0.0,
    BUILD_END 29.4,
    TESTSETUP_START 0.0,
    TESTSETUP_END 0.4,
    TEST_START 0.0,
    TEST_END 2.4,
    FINISH_END 0.2

See test results for failed build of commit 10ea7d962c

@AppVeyorBot

Copy link
Copy Markdown

See test results for failed build of commit 1bc860a915

Comment thread source/nvwave.py Outdated
Comment thread source/nvwave.py Outdated
Comment thread source/nvwave.py Outdated
Comment thread source/gui/settingsDialogs.py Outdated
Comment thread source/globalCommands.py
Comment thread source/globalCommands.py Outdated
@seanbudd seanbudd marked this pull request as draft February 19, 2024 01:00
@beqabeqa473

Copy link
Copy Markdown
Contributor Author

@seanbudd I've modified _synthWarningDialog to accept synth instance, while at it, i've made that synthDriverHandler.getSynthList now returns SynthDriver classes rather than names and descriptions and modified synth selection dialog to make it working with new layout.

@seanbudd seanbudd marked this pull request as ready for review February 19, 2024 23:25

def updateSynthesizerList(self):
driverList=getSynthList()
self.synthNames=[x[0] for x in driverList]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be removed, this is an API breaking change.
I'd encourage reverting these unrelated changes.



def getSynthList() -> List[Tuple[str, str]]:
def getSynthList() -> list[SynthDriver]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't change the return type of this function, it is an API breaking change

Comment thread source/nvwave.py
currentSynth = getSynth()
if not setSynth(currentSynth.name):
return False
onSynthSetFailed(currentSynth)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onSynthSetFailed(currentSynth)
if onSynthSetFailed is not None:
onSynthSetFailed(currentSynth)

Comment thread source/nvwave.py
def setOutputDevice(
deviceName: str,
onSynthSetFailed: Callable[[SynthDriver], None] | None = None,
):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
):
) -> bool:

Comment thread source/nvwave.py
currentSynth = getSynth()
if not setSynth(currentSynth.name):
onSynthSetFailed(currentSynth)
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return False

Comment thread source/nvwave.py
Comment on lines +691 to +692
@param deviceName: The device name.
@param onSynthSetFailed: A callable to show an error if setting synthesizer fails.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@param deviceName: The device name.
@param onSynthSetFailed: A callable to show an error if setting synthesizer fails.
:param deviceName: The device name.
:param onSynthSetFailed: A callable to show an error if setting synthesizer fails.
:returns: True if setting the output device succeeds

Comment thread source/globalCommands.py
@staticmethod
def _cycleOutputAudioDevices(forward: bool = True):
"""Cycle through available output devices.
@param forward: boolean flag if false cycles in backward direction.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanbudd seanbudd marked this pull request as draft February 20, 2024 05:18
@Adriani90

Copy link
Copy Markdown
Collaborator

@beqabeqa473 are you still working on this?

@beqabeqa473

Copy link
Copy Markdown
Contributor Author

I will try to revisit this in the near feature.

@seanbudd

seanbudd commented Sep 1, 2024

Copy link
Copy Markdown
Member

Closing as abandoned

@seanbudd seanbudd closed this Sep 1, 2024
@seanbudd seanbudd added the Abandoned requested reports or updates are missing since more than 1 year, author or users are not available. label Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Abandoned requested reports or updates are missing since more than 1 year, author or users are not available. conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

give the availability of a shortcut key to change from one sound output to another

8 participants