Skip to content

Commit

Permalink
Use sound_lib/bass for playback. This majorly improves responsiveness…
Browse files Browse the repository at this point in the history
… and sounds no longer interrupt each other. In addition, adds volume slider.
  • Loading branch information
Bryn committed Jul 17, 2023
1 parent f10b7c8 commit 61ac0d7
Show file tree
Hide file tree
Showing 165 changed files with 5,634 additions and 7 deletions.
33 changes: 26 additions & 7 deletions globalPlugins/typing_settings/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import nvwave
from .sound_lib import output
from .sound_lib.stream import FileStream
import globalPluginHandler
import speech
import config
Expand All @@ -13,13 +14,26 @@
from scriptHandler import script
from gui import SettingsPanel, NVDASettingsDialog, guiHelper
from controlTypes import STATE_READONLY, STATE_EDITABLE

bass_output=output.Output()
chans=[]

#Internal
def play_sound_bass(filename,volume=-1):
global chans
chans.append(FileStream(file=filename, autofree=True))
chans[len(chans)-1].volume=(config.conf["typing_settings"]["volume"]/100 if volume==-1 else volume/100)
chans[len(chans)-1].play(True)
while len(chans)>10: chans.remove(chans[0])

def confinit():
confspec = {
"typingsnd": "boolean(default=true)",
"typing_sound": f"string(default={get_sounds_folders()[0]})",
"speak_characters": "integer(default=2)",
"speak_words": "integer(default=2)",
"speak_on_protected":"boolean(default=True)"}
"speak_on_protected":"boolean(default=True)",
"volume": "integer(default=100)"}
config.confspec["typing_settings"] = confspec

addonHandler.initTranslation()
Expand Down Expand Up @@ -63,19 +77,23 @@ def makeSettings(self, settingsSizer):
self.playTypingSounds.SetValue(config.conf["typing_settings"]["typingsnd"])
self.speakPasswords = sHelper.addItem(wx.CheckBox(self, label=_("speak passwords")))
self.speakPasswords.SetValue(config.conf["typing_settings"]["speak_on_protected"])
self.volumeSliderLabel = sHelper.addItem(wx.StaticText(self, label=_("Volume")))
self.volumeSlider = sHelper.addItem(wx.Slider(self))
self.volumeSlider.SetValue(config.conf["typing_settings"]["volume"])
try:
self.speakCharacters.SetSelection(config.conf["typing_settings"]["speak_characters"])
except:
self.speakCharacters.SetSelection(0)
try:
self.speakWords.SetSelection(config.conf["typing_settings"]["speak_characters"])
self.speakWords.SetSelection(config.conf["typing_settings"]["speak_words"])
except:
self.speakWords.SetSelection(0)
self.OnChangeTypingSounds(None)
self.onChange(None)
self.playTypingSounds.Bind(wx.EVT_CHECKBOX, self.OnChangeTypingSounds)
self.typingSound.Bind(wx.EVT_CHOICE, self.onChange)
self.sounds.Bind(wx.EVT_CHOICE, self.onPlay)
self.volumeSlider.Bind(wx.EVT_SLIDER, self.onPlay)

def postInit(self):
self.typingSound.SetFocus()
Expand All @@ -92,14 +110,15 @@ def onChange(self, event):
except: pass

def onPlay(self, event):
nvwave.playWaveFile(f"{effects_dir}/{self.typingSound.GetStringSelection()}/{self.sounds.GetStringSelection()}", True)
play_sound_bass(f"{effects_dir}/{self.typingSound.GetStringSelection()}/{self.sounds.GetStringSelection()}",volume=self.volumeSlider.GetValue())

def onSave(self):
config.conf["typing_settings"]["typing_sound"] = self.typingSound.GetStringSelection()
config.conf["typing_settings"]["speak_characters"] = self.speakCharacters.GetSelection()
config.conf["typing_settings"]["speak_words"] = self.speakWords.GetSelection()
config.conf["typing_settings"]["speak_on_protected"] = self.speakPasswords.GetValue()
config.conf["typing_settings"]["typingsnd"] = self.playTypingSounds.GetValue()
config.conf["typing_settings"]["volume"] = self.volumeSlider.GetValue()

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
def __init__(self, *args, **kwargs):
Expand All @@ -120,12 +139,12 @@ def event_gainFocus(self, object, nextHandler):
def event_typedCharacter(self, obj, nextHandler, ch):
if self.IsEditable(obj) and config.conf["typing_settings"]["typingsnd"]:
if ch ==" ":
nvwave.playWaveFile(os.path.join(effects_dir, config.conf['typing_settings']['typing_sound'], "space.wav"), True)
play_sound_bass(os.path.join(effects_dir, config.conf['typing_settings']['typing_sound'], "space.wav"))
elif ch == "\b":
nvwave.playWaveFile(os.path.join(effects_dir, config.conf['typing_settings']['typing_sound'], "delete.wav"), True)
play_sound_bass(os.path.join(effects_dir, config.conf['typing_settings']['typing_sound'], "delete.wav"))
else:
count = self.SoundsCount(config.conf["typing_settings"]["typing_sound"])
nvwave.playWaveFile(os.path.join(effects_dir, config.conf['typing_settings']['typing_sound'], "typing.wav" if count<=0 else f"typing_{randint(1, count)}.wav"), True)
play_sound_bass(os.path.join(effects_dir, config.conf['typing_settings']['typing_sound'], "typing.wav" if count<=0 else f"typing_{randint(1, count)}.wav"))
nextHandler()

def SoundsCount(self, name):
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions globalPlugins/typing_settings/libloader/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .libloader import *

__version__ = 0.21
__author__ = "Christopher Toth"
__author_email__ = "[email protected]"
__doc__ = """
Quickly and easily load shared libraries from various platforms. Also includes a libloader.com module for loading com modules on Windows.
"""
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
22 changes: 22 additions & 0 deletions globalPlugins/typing_settings/libloader/com.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from pywintypes import com_error
from win32com.client import gencache


def prepare_gencache():
gencache.is_readonly = False
gencache.GetGeneratePath()


def load_com(*names):
if gencache.is_readonly:
prepare_gencache()
result = None
for name in names:
try:
result = gencache.EnsureDispatch(name)
break
except com_error:
continue
if result is None:
raise com_error("Unable to load any of the provided com objects.")
return result
66 changes: 66 additions & 0 deletions globalPlugins/typing_settings/libloader/libloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import ctypes
import collections
import platform
import os
import sys


TYPES = {
"Linux": {
"loader": ctypes.CDLL,
"functype": ctypes.CFUNCTYPE,
"prefix": "lib",
"extension": ".so",
},
"Darwin": {
"loader": ctypes.CDLL,
"functype": ctypes.CFUNCTYPE,
"prefix": "lib",
"extension": ".dylib",
},
}
if platform.system() == "Windows":
TYPES["Windows"] = {
"loader": ctypes.WinDLL,
"functype": ctypes.WINFUNCTYPE,
"prefix": "",
"extension": ".dll",
}


class LibraryLoadError(OSError):
pass


def load_library(library, x86_path=".", x64_path=".", *args, **kwargs):
lib = find_library_path(library, x86_path=x86_path, x64_path=x64_path)
loaded = _do_load(lib, *args, **kwargs)
if loaded is not None:
return loaded
raise LibraryLoadError(
"unable to load %r. Provided library path: %r" % (library, path)
)


def _do_load(file, *args, **kwargs):
loader = TYPES[platform.system()]["loader"]
return loader(file, *args, **kwargs)


def find_library_path(libname, x86_path=".", x64_path="."):
libname = "%s%s" % (TYPES[platform.system()]["prefix"], libname)
if platform.architecture()[0] == "64bit":
path = os.path.join(x64_path, libname)
else:
path = os.path.join(x86_path, libname)
ext = get_library_extension()
path = "%s%s" % (path, ext)
return os.path.abspath(path)


def get_functype():
return TYPES[platform.system()]["functype"]


def get_library_extension():
return TYPES[platform.system()]["extension"]
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
24 changes: 24 additions & 0 deletions globalPlugins/typing_settings/platform_utils/blackhole.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Replacement for py2exe distributed module
# Avoids the use of the standard py2exe console.
# Just import this file and it should go away

import sys

if hasattr(sys, "frozen"): # true only if we are running as a py2exe app

class Blackhole(object):
"""Mock file object that does nothing."""

def write(self, text):
pass

def flush(self):
pass

def isatty(self):
return False

sys.stdout = Blackhole()
sys.stderr = Blackhole()
del Blackhole
del sys
128 changes: 128 additions & 0 deletions globalPlugins/typing_settings/platform_utils/clipboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import platform


def set_text_windows(text):
"""
Args:
text:
Returns:
"""
import win32clipboard
import win32con

win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(text, win32con.CF_UNICODETEXT)
finally:
win32clipboard.CloseClipboard()


def set_text_gtk(text):
"""
Args:
text:
Returns:
"""
import gtk

cb = gtk.Clipboard()
cb.set_text(text)
cb.store()


def set_text_osx(text):
"""
Args:
text:
Returns:
"""
scrap = True
try:
import Carbon.Scrap
except ModuleNotFoundError:
scrap = False
if scrap:
Carbon.Scrap.ClearCurrentScrap()
scrap = Carbon.Scrap.GetCurrentScrap()
scrap.PutScrapFlavor("TEXT", 0, text)
else:
try:
text = text.encode()
except AttributeError:
pass
import subprocess

s = subprocess.Popen("pbcopy", stdin=subprocess.PIPE)
s.communicate(text)


def set_text(text):
"""Copies text to the clipboard.
Args:
text:
Returns:
"""
plat = platform.system()
if plat == "Windows":
set_text_windows(text)
elif plat == "Linux":
set_text_gtk(text)
elif plat == "Darwin":
set_text_osx(text)
else:
raise NotImplementedError("Cannot set clipboard text on platform %s" % plat)


copy = set_text


def get_text_windows():
""" """
import win32clipboard
import win32con

win32clipboard.OpenClipboard()
try:
text = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
finally:
win32clipboard.CloseClipboard()
return text


def get_text_osx():
""" """
import subprocess

s = subprocess.Popen("pbpaste", stdout=subprocess.PIPE)
result = s.communicate()[0]
try:
result = result.decode()
except UnicodeDecodeError:
pass
return result


def get_text():
""" """
plat = platform.system()
if plat == "Windows":
return get_text_windows()
elif plat == "Darwin":
return get_text_osx()
else:
raise NotImplementedError(
"Cannot get text from clipboard on platform %s" % plat
)
50 changes: 50 additions & 0 deletions globalPlugins/typing_settings/platform_utils/idle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import platform

system = platform.system()


def get_user_idle_time():
"""
Args:
Returns:
This is normally obtained from a lack of keyboard and/or mouse input.
"""
if system == "Windows":
return get_user_idle_time_windows()
elif system == "Darwin":
return get_user_idle_time_mac()
raise NotImplementedError("This function is not yet implemented for %s" % system)


def get_user_idle_time_windows():
""" """
from ctypes import Structure, windll, c_uint, sizeof, byref

class LASTINPUTINFO(Structure):
""" """
_fields_ = [("cbSize", c_uint), ("dwTime", c_uint)]

lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = sizeof(lastInputInfo)
windll.user32.GetLastInputInfo(byref(lastInputInfo))
millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
return millis / 1000.0


def get_user_idle_time_mac():
""" """
import subprocess
import re

s = subprocess.Popen(("ioreg", "-c", "IOHIDSystem"), stdout=subprocess.PIPE)
data = s.communicate()[0]
expression = "HIDIdleTime.*"
try:
data = data.decode()
r = re.compile(expression)
except UnicodeDecodeError:
r = re.compile(expression.encode())
return int(r.findall(data)[0].split(" = ")[1]) / 1000000000
Loading

0 comments on commit 61ac0d7

Please sign in to comment.