Add rate boost support for SAPI5 voices (#17610)
Closes #17606
Summary of the issue:
SAPI5 voices do not support rate boosting, but some of the SAPI5 voices are not fast enough even at the highest rate for some experienced users.
Description of user facing changes
The "rate boost" option will be available to users when using SAPI5 voices, which supports rates ranging from 0.5x to 6x.
If rate boost is disabled, the behavior will be the same as before.
Description of development approach
The Sonic library, which is also used by eSpeak NG, is used to change the speed when rate boost is enabled.
When rate boost is enabled, to preserve quality, the SAPI5 voice is set to output at its original speed (1x), and then Sonic is used to change the speed of the original audio. When rate boost is disabled, Sonic is no longer used to change the speed, and the rate of the SAPI5 voice itself is set instead to preserve the previous behavior.
As Sonic is used by eSpeak NG, it has already been included in the NVDA repo as a submodule (`/include/sonic/`). However, in eSpeak NG, it is compiled as a static library, which cannot be easily reused. So some build steps are changed to build Sonic as a DLL, `sonic.dll`, instead, which is installed in the `synthDrivers` folder. eSpeak-NG is also changed to dynamically link to `sonic.dll`.
A new file `_sonic.py` is created inside `synthDrivers` to handle the interoperation with `sonic.dll`. There's `initialize()` to load the Sonic DLL which is called in `speech.initialize()`, and there's a wrapper class `SonicStream` for the Sonic stream mode functions.
The SAPI5 synthesizer now passes the audio through a `SonicStream` first, before sending the audio to the `WavePlayer`. To speed up audio processing in Sonic, which uses 16-bit integer wave format internally, we explicitly choose a 16-bit wave format for the SAPI5 voice and the `WavePlayer` to avoid unnecessary format conversion.
This is the approach I chose currently. The implementation details are open for discussion. Other ways I've thought of:
- Move the Sonic library inside nvdaHelperLocal, and process the audio with Sonic in `WasapiPlayer` before feeding the data to the device. Then add some functions such as `getRate` and `setRate` to the `WavePlayer`.
- Implement some kind of "audio plugin" system to allow easy modification to audio streams.
Testing strategy:
Seemed to work on my system.
Known issues with pull request:
None