Skip to content

Add WASAPI toggle to SAPI5#18352

Merged
SaschaCowley merged 10 commits into
nvaccess:betafrom
gexgd0419:add-wasapi-switch
Jul 15, 2025
Merged

Add WASAPI toggle to SAPI5#18352
SaschaCowley merged 10 commits into
nvaccess:betafrom
gexgd0419:add-wasapi-switch

Conversation

@gexgd0419

@gexgd0419 gexgd0419 commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

Link to issue number:

Closes #18309.

Summary of the issue:

Users cannot turn off WASAPI for SAPI5 yet.

Description of user facing changes:

Add a synthesizer setting called useWasapi, which is displayed as a checkbox labeled Use modern audio output system (WASAPI) in the voice settings when the built-in SAPI5 synthesizer is selected.

Description of developer facing changes:

None

Description of development approach:

A new SynthSetting based on BooleanDriverSetting called useWasapi is added and defaults to True. It is not in the settings ring.

Legacy SAPI5 code are re-introduced to be used when WASAPI for SAPI5 is disabled.

Testing strategy:

Tested manually.

Known issues with pull request:

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.

@coderabbitai summary

@gexgd0419

Copy link
Copy Markdown
Contributor Author

@seanbudd @SaschaCowley What's your thought on the name, location, and display text of this new synth setting? (It's added at the end of the synth settings list for now)

Should the existing config config.conf["speech"]["useWASAPIForSAPI4"] be migrated? How should I do this?

@gexgd0419 gexgd0419 changed the base branch from master to beta June 27, 2025 05:24
@SaschaCowley

Copy link
Copy Markdown
Member

@gexgd0419 note I haven't reviewed any code:

  • I'd rather the toggle be a FeatureFlag so we can change the default behaviour in future
  • I think you can probably change the display name of the setting to "Use modern audio output", and note in the user guide that this pertains to WASAPI.
  • Ideally, we should have config.conf["speech"]["sapi4"]["useWASAPI"] and config.conf["speech"]["sapi5"]["useWASAPI"]
    • To ensure backwards compatibility, ["speech"]["useWASAPIForSAPI4"] and ["speech"]["sapi4"]["useWASAPI"]` should be linked. See for example

      nvda/source/config/__init__.py

      Lines 1308 to 1356 in e1d0d9d

      # Alias [documentFormatting][reportFontAttributes] for backwards compatibility.
      # TODO: Comment out in 2025.1.
      if version_year < 2025 and NVDAState._allowDeprecatedAPI():
      self._linkDeprecatedValues(key, val)
      def _linkDeprecatedValues(self, key: aggregatedSection._cacheKeyT, val: aggregatedSection._cacheValueT):
      """Link deprecated config keys and values to their replacements.
      Args:
      key: The configuration key to link to its new or old counterpart.
      val: The value associated with the configuration key.
      postconditions:
      - If self.path is "documentFormatting":
      - If key is "reportFontAttributes":
      - If val is True, "documentFormatting.fontAttributeReporting" is set to OutputMode.SPEECH_AND_BRAILLE, otherwise, it is set to OutputMode.OFF.
      - If key is "fontAttributeReporting":
      - if val is OutputMode.OFF, "documentFormatting.reportFontAttributes" is set to False, otherwise, it is set to True.
      """
      match self.path:
      case ("documentFormatting",):
      match key:
      case "fontAttributeReporting":
      # Alias documentFormatting.fontAttributeReporting to documentFormatting.reportFontAttributes for backwards compatibility.
      key = "reportFontAttributes"
      val = bool(val)
      case "reportFontAttributes":
      # Alias documentFormatting.reportFontAttributes to documentFormatting.fontAttributeReporting for forwards compatibility.
      log.warning(
      "documentFormatting.reportFontAttributes is deprecated. Use documentFormatting.fontAttributeReporting instead.",
      # Include stack info so testers can report warning to add-on author.
      stack_info=True,
      )
      key = "fontAttributeReporting"
      val = OutputMode.SPEECH_AND_BRAILLE if val else OutputMode.OFF
      case _:
      # We don't care about other keys in this section.
      return
      case _:
      # We don't care about other sections.
      return
      # Update the value in the most recently activated profile.
      # If we have reached this point, we must have a new key and value to set.
      self._getUpdateSection()[key] = val
      self._cache[key] = val

@gexgd0419 gexgd0419 force-pushed the add-wasapi-switch branch from 45b91c3 to 4c8e6a3 Compare June 27, 2025 05:33
@gexgd0419

Copy link
Copy Markdown
Contributor Author

BooleanDriverSetting is represented as a checkbox. If we want to use a FeatureFlag instead, should a new kind of DriverSetting be added? Or maybe this shouldn't be put inside "driver settings"?

@SaschaCowley

Copy link
Copy Markdown
Member

Hmm. I guess we have 3 options:

  • Keep this as a boolean driver setting.
    • Removes the advantages of feature flags
    • It is tried and tested, and keeps the code clean
  • Add a new type of driver setting that supports feature flags (or similar)
    • More effort
    • More testing required
    • Are there any other instances where this would be useful?
  • Move this out of the synth driver itself.
    • For instance, ["speech"]["useWASAPIForSAPI4"] and ["speech"]["useWASAPIForSAPI5"].
    • We could then only show them when the relevant engine is selected.
    • Slightly "dirty" approach

@seanbudd, @michaelDCurran do you have any thoughts? I am leaning towards just keeping this as a checkbox (1) or not having it as part of the driver settings (3). I don't think (2) is worth the effort at this point. Given the time constraints and the tidiness of the code, I think I would probably go with option 1, but I'd like a second opinion.

@CyrilleB79

Copy link
Copy Markdown
Contributor

I have not reviewed this PR. I wonder:
Wasn't there a difficultiy integrating feature flags in drivers? I guess someone had tried this recently, maybe @LeonarddeR

@LeonarddeR

Copy link
Copy Markdown
Collaborator

I think that the magic code that creates gui items based on config spec doesn't yet support feature flags.

@gexgd0419

Copy link
Copy Markdown
Contributor Author

I think that the magic code that creates gui items based on config spec doesn't yet support feature flags.

If it is useful enough, this can be implemented, I think.

If there are going to be "per-voice driver settings", having this toggle as a driver setting may make it easier to fit in.

By the way, when WASAPI for SAPI5 is disabled, "rate boost" will also be disabled. Should anything be done to disable or hide the "rate boost" checkbox in that case, or just leave it there?

@CyrilleB79

Copy link
Copy Markdown
Contributor

By the way, when WASAPI for SAPI5 is disabled, "rate boost" will also be disabled. Should anything be done to disable or hide the "rate boost" checkbox in that case, or just leave it there?

If possible, it would be better to disable (gray out) the rate boot checkbox in this case to denote that it does not apply in this situation.

@gexgd0419

Copy link
Copy Markdown
Contributor Author

If possible, it would be better to disable (gray out) the rate boot checkbox in this case to denote that it does not apply in this situation.

"Rate boost" is a driver setting, so the checkbox control is generated by the "magic code" in AutoSettingsMixin. Not sure if it is a good idea to disable such a control, or how I can fetch such a control after creation.

@seanbudd seanbudd added the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Jul 1, 2025
@gexgd0419

Copy link
Copy Markdown
Contributor Author

Hmm. I guess we have 3 options:

  • Keep this as a boolean driver setting.
    • Removes the advantages of feature flags
    • It is tried and tested, and keeps the code clean
  • Add a new type of driver setting that supports feature flags (or similar)
    • More effort
    • More testing required
    • Are there any other instances where this would be useful?
  • Move this out of the synth driver itself.
    • For instance, ["speech"]["useWASAPIForSAPI4"] and ["speech"]["useWASAPIForSAPI5"].
    • We could then only show them when the relevant engine is selected.
    • Slightly "dirty" approach

Any guidance on what I should do? @SaschaCowley @seanbudd

@zstanecic

Copy link
Copy Markdown
Contributor

We need to keep compatibility with older NVDA.

@seanbudd seanbudd marked this pull request as ready for review July 7, 2025 03:36
Copilot AI review requested due to automatic review settings July 7, 2025 03:36
@seanbudd seanbudd requested review from a team as code owners July 7, 2025 03:36
@seanbudd seanbudd requested review from Qchristensen and seanbudd July 7, 2025 03:36
@seanbudd

seanbudd commented Jul 7, 2025

Copy link
Copy Markdown
Member

@gexgd0419 - can you mark PRs ready for review when they are awaiting feedback from us

@seanbudd seanbudd added the blocked/needs-product-decision A product decision needs to be made. Decisions about NVDA UX or supported use-cases. label Jul 7, 2025

Copilot AI left a comment

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.

Pull Request Overview

This PR introduces a new Use modern audio output system (WASAPI) toggle for both SAPI4 and SAPI5 synthesizers, replacing the old advanced-only SAPI4 setting and re-enabling legacy paths when WASAPI is turned off.

  • Add new useWasapi setting via BooleanDriverSetting, exposed in voice settings for SAPI4/SAPI5
  • Implement WASAPI vs. legacy audio initialization and audio ducking logic in both drivers
  • Update user guide, GUI dialogs, and remove deprecated useWASAPIForSAPI4 config spec

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
user_docs/en/userGuide.md Document new WASAPI setting under voice settings and remove old SAPI4 advanced UI
source/synthDrivers/sapi5.py Add SPAudioState enum, audio ducking integration, WASAPI/legacy init and handling
source/synthDrivers/sapi4.py Add UseWasapiSetting, useWasapi property, toggle between WASAPI and MMAudio
source/synthDriverHandler.py Introduce UseWasapiSetting factory for driver settings
source/gui/settingsDialogs.py Include useWasapi in standardSettings and remove old useWASAPIForSAPI4 UI
source/config/configSpec.py Remove deprecated useWASAPIForSAPI4 configuration entry
Comments suppressed due to low confidence (4)

source/config/configSpec.py:49

  • The removal of the legacy 'useWASAPIForSAPI4' config spec will reset existing user preferences. Consider adding migration logic to preserve users’ existing settings.
	trimLeadingSilence = boolean(default=true)

source/synthDrivers/sapi4.py:844

  • [nitpick] New WASAPI toggle has been introduced; consider adding unit and integration tests to cover enabling/disabling audio paths for the SAPI4 synthesizer.
	supportedSettings = [SynthDriver.VoiceSetting(), SynthDriver.UseWasapiSetting()]

source/synthDrivers/sapi5.py:252

  • [nitpick] A new setting for WASAPI support has been added; please add tests verifying both paths (WASAPI enabled and disabled) for SAPI5 audio initialization and streaming behavior.
		SynthDriver.UseWasapiSetting(),

source/synthDrivers/sapi5.py:39

  • IntEnum is not imported in this file, so defining SPAudioState(IntEnum) will raise a NameError. Please add 'from enum import IntEnum' to the imports.
class SPAudioState(IntEnum):

@seanbudd seanbudd removed the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Jul 7, 2025
@seanbudd seanbudd added this to the 2025.2 milestone Jul 10, 2025
@seanbudd seanbudd requested a review from SaschaCowley July 10, 2025 02:03
Comment thread source/synthDriverHandler.py Outdated
Comment thread source/config/configSpec.py
Comment thread source/gui/settingsDialogs.py Outdated
Comment thread source/synthDrivers/sapi5.py Outdated
Comment thread source/synthDrivers/sapi5.py Outdated
Comment thread source/synthDrivers/sapi5.py Outdated
Comment thread source/synthDrivers/sapi4.py Outdated
Comment thread source/gui/settingsDialogs.py
@seanbudd

Copy link
Copy Markdown
Member

@SaschaCowley

I think we should go with (1) on your comment #18352 (comment)

In regards to (3) I think we need to keep the old SAPI4 config option and sync with it, or not add this to SAPI4 until 2026.1

Co-authored-by: Sean Budd <seanbudd123@gmail.com>
@gexgd0419

Copy link
Copy Markdown
Contributor Author

So should I keep both toggles for SAPI4, or keep only the old one?

@SaschaCowley

Copy link
Copy Markdown
Member

@gexgd0419 please keep only the old toggle for SAPI4 for now

@gexgd0419 gexgd0419 marked this pull request as draft July 11, 2025 04:39
@seanbudd seanbudd removed the blocked/needs-product-decision A product decision needs to be made. Decisions about NVDA UX or supported use-cases. label Jul 14, 2025
@gexgd0419 gexgd0419 changed the title Add WASAPI toggle to SAPI4 and SAPI5 Add WASAPI toggle to SAPI5 Jul 14, 2025
@gexgd0419

Copy link
Copy Markdown
Contributor Author

This pull request is updated to contain only changes to SAPI5. Not sure why #18310 isn't unlinked though.

Should the "(WASAPI)" part in the description Use modern audio output system (WASAPI) be taken out? Should the word "WASAPI" in useWasapi be written in all-caps or not?

@gexgd0419 gexgd0419 marked this pull request as ready for review July 14, 2025 06:47
@seanbudd

Copy link
Copy Markdown
Member

I can't seem to manually unlink it. Maybe we don't worry about that.

I think keeping WASAPI there as is, is fine

Comment thread source/synthDriverHandler.py

@Qchristensen Qchristensen left a comment

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.

Reads well, good work.

@SaschaCowley SaschaCowley merged commit aebd5b8 into nvaccess:beta Jul 15, 2025
15 of 16 checks passed
@wmhn1872265132 wmhn1872265132 mentioned this pull request Jul 15, 2025
5 tasks
@gexgd0419 gexgd0419 deleted the add-wasapi-switch branch July 15, 2025 07:01
@gexgd0419 gexgd0419 mentioned this pull request Jul 15, 2025
5 tasks
seanbudd pushed a commit that referenced this pull request Jul 15, 2025
Fixup of #18352.
Summary of the issue:

When WASAPI is turned off, bookmark events are not handled properly.
Description of user facing changes:

Users might not notice that, because although the bookmarks are not handled in time, they are still processed at the end of the utterance.
Description of developer facing changes:

None
Description of development approach:

Code that handles the bookmark events when WASAPI is off is added.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

remove SAPI4 from NVDA Make WASAPI togglable for SAPI 5 - and off by default

8 participants