Skip to content

Commit

Permalink
An alternative implementation of audio ducking for Windows build 1498…
Browse files Browse the repository at this point in the history
…6 and up: Play silence while we expect to be ducked.

Fixes #6684
  • Loading branch information
michaelDCurran committed Jan 26, 2017
1 parent d9b0092 commit b3cb587
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 1 deletion.
14 changes: 13 additions & 1 deletion source/audioDucking.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import time
import config
from logHandler import log
import winVersion
from silencePlayer import SilencePlayer

def _isDebug():
return config.conf["debugLog"]["audioDucking"]
Expand All @@ -27,6 +29,7 @@ def __del__(self):
AUDIODUCKINGMODE_NONE=0
AUDIODUCKINGMODE_OUTPUTTING=1
AUDIODUCKINGMODE_ALWAYS=2
WINVERSION_DYNAMICDUCKING_MIN=14986

audioDuckingModes=[
# Translators: An audio ducking mode which specifies how NVDA affects the volume of other applications.
Expand All @@ -50,16 +53,25 @@ def __del__(self):
_duckingRefCountLock = threading.RLock()
_modeChangeEvent=None
_lastDuckedTime=0
_silencePlayer=None

def _setDuckingState(switch):
global _lastDuckedTime
global _lastDuckedTime, _silencePlayer
with _duckingRefCountLock:
import gui
ATWindow=gui.mainFrame.GetHandle()
if switch:
oledll.oleacc.AccSetRunningUtilityState(ATWindow,ANRUS_ducking_AUDIO_ACTIVE|ANRUS_ducking_AUDIO_ACTIVE_NODUCK,ANRUS_ducking_AUDIO_ACTIVE|ANRUS_ducking_AUDIO_ACTIVE_NODUCK)
# In Certain Windows builds, dynamic audio ducking was introduced by Microsoft that conflicts with our own audio ducking code.
# To force their dynamic ducking back to static ducking, play silence for the entire length we want to be truely ducked for.
if winVersion.winVersion.build>=WINVERSION_DYNAMICDUCKING_MIN:
if not _silencePlayer:
_silencePlayer=SilencePlayer()
_silencePlayer.enable()
_lastDuckedTime=time.time()
else:
if _silencePlayer:
_silencePlayer.disable()
oledll.oleacc.AccSetRunningUtilityState(ATWindow,ANRUS_ducking_AUDIO_ACTIVE|ANRUS_ducking_AUDIO_ACTIVE_NODUCK,ANRUS_ducking_AUDIO_ACTIVE_NODUCK)

def _ensureDucked():
Expand Down
46 changes: 46 additions & 0 deletions source/silencePlayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import threading
import nvwave
import config

class SilencePlayer(threading.Thread):
"""
Plays silence on the configured audio output device, to aide in audio ducking.
Once instantiated, call enable() to start the silence, and disable() to stop.
"""

_sampleRate=22050
_bitDepth=16
_channels=1

def __init__(self):
# Create silence of about 200 ms in length
self._data='\x00'*(self._sampleRate/2)
self._wavePlayer=None
self._initEvent=threading.Event()
self._wakeEvent=threading.Event()
super(SilencePlayer,self).__init__()
self.daemon=True
self.start()

def run(self):
self._initEvent.set()
# Keep either waiting on an event, or feeding silence to the audio device
while True:
if not self._wakeEvent.isSet():
# Ensure that the correct audio device is used each time it starts playing silence
self._wavePlayer=None
self._wakeEvent.wait()
if not self._wavePlayer:
self._wavePlayer=nvwave.WavePlayer(channels=self._channels, samplesPerSec=self._sampleRate, bitsPerSample=self._bitDepth, outputDevice=config.conf["speech"]["outputDevice"],wantDucking=False)
self._wavePlayer.feed(self._data)

def disable(self):
""" Stops playing silence"""
if self._wakeEvent.isSet():
self._wakeEvent.clear()
self._wavePlayer.stop()

def enable(self):
"""Starts playing silence"""
if not self._wakeEvent.isSet():
self._wakeEvent.set()

0 comments on commit b3cb587

Please sign in to comment.