Make SAPI4 voices use WASAPI (#17718)
This PR makes the built-in SAPI4 synthesizer use WASAPI to output audio, so that old code related to WinMM can be removed entirely.
Description of user facing changes
SAPI4 voices should work as usual.
Features supported by WavePlayer, such as audio ducking, leading silence trimming, and keeping audio device awake (#17571) will be able to work with SAPI4 voices.
Description of development approach
Create a class to implement IAudio and IAudioDest, so that it can be used as an audio output destination to replace the SAPI4 built-in MMAudioDest which uses WinMM.
SAPI4 performs audio data output on the main thread. SAPI4 expects audio data writes to be a non-blocking operation, and it should return AUDERR_NOTENOUGHDATA when the buffer is full. Unfortunately, that's not how WavePlayer works, and WavePlayer.feed blocks the current thread until there's enough space in the buffer. So a dedicated thread is created to feed data to WavePlayer, and audio data from SAPI4 will be put in a queue first to prevent blocking the thread. Bookmarks from SAPI4 will be put in the same queue.
WavePlayer.feed returns before the audio is finished playing, but WavePlayer can only check and invoke callback functions when WavePlayer.feed or WavePlayer.sync is called. If we just keep on waiting for the next audio chunk in the queue, WavePlayer will have no chance to call the callback functions when there is no chunk. So WavePlayer.feed should be called periodically, regardless of whether there's audio or bookmark in the queue.