1# Orca 2# 3# Copyright 2016 Igalia, S.L. 4# 5# Author: Joanmarie Diggs <jdiggs@igalia.com> 6# 7# This library is free software; you can redistribute it and/or 8# modify it under the terms of the GNU Lesser General Public 9# License as published by the Free Software Foundation; either 10# version 2.1 of the License, or (at your option) any later version. 11# 12# This library is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public 18# License along with this library; if not, write to the 19# Free Software Foundation, Inc., Franklin Street, Fifth Floor, 20# Boston MA 02110-1301 USA. 21 22"""Utilities for obtaining sounds to be presented for objects.""" 23 24__id__ = "$Id:$" 25__version__ = "$Revision:$" 26__date__ = "$Date:$" 27__copyright__ = "Copyright (c) 2016 Igalia, S.L." 28__license__ = "LGPL" 29 30import gi 31gi.require_version('Atspi', '2.0') 32from gi.repository import Atspi 33 34import os 35import pyatspi 36 37from . import generator 38from . import settings_manager 39 40_settingsManager = settings_manager.getManager() 41 42METHOD_PREFIX = "_generate" 43 44 45class Icon: 46 """Sound file representing a particular aspect of an object.""" 47 48 def __init__(self, location, filename): 49 self.path = os.path.join(location, filename) 50 51 def __str__(self): 52 return 'Icon(path: %s, isValid: %s)' % (self.path, self.isValid()) 53 54 def isValid(self): 55 return os.path.isfile(self.path) 56 57class Tone: 58 """Tone representing a particular aspect of an object.""" 59 60 SINE_WAVE = 0 61 SQUARE_WAVE = 1 62 SAW_WAVE = 2 63 TRIANGLE_WAVE = 3 64 SILENCE = 4 65 WHITE_UNIFORM_NOISE = 5 66 PINK_NOISE = 6 67 SINE_WAVE_USING_TABLE = 7 68 PERIODIC_TICKS = 8 69 WHITE_GAUSSIAN_NOISE = 9 70 RED_NOISE = 10 71 INVERTED_PINK_NOISE = 11 72 INVERTED_RED_NOISE = 12 73 74 def __init__(self, duration, frequency, volumeMultiplier=1, wave=SINE_WAVE): 75 self.duration = duration 76 self.frequency = min(max(0, frequency), 20000) 77 self.volume = _settingsManager.getSetting('soundVolume') * volumeMultiplier 78 self.wave = wave 79 80 def __str__(self): 81 return 'Tone(duration: %s, frequency: %s, volume: %s, wave: %s)' \ 82 % (self.duration, self.frequency, self.volume, self.wave) 83 84class SoundGenerator(generator.Generator): 85 """Takes accessible objects and produces the sound(s) to be played.""" 86 87 def __init__(self, script): 88 super().__init__(script, 'sound') 89 self._sounds = os.path.join(_settingsManager.getPrefsDir(), 'sounds') 90 91 def _convertFilenameToIcon(self, filename): 92 icon = Icon(self._sounds, filename) 93 if icon.isValid(): 94 return icon 95 96 return None 97 98 def generateSound(self, obj, **args): 99 """Returns an array of sounds for the complete presentation of obj.""" 100 101 return self.generate(obj, **args) 102 103 ##################################################################### 104 # # 105 # State information # 106 # # 107 ##################################################################### 108 109 def _generateAvailability(self, obj, **args): 110 """Returns an array of sounds indicating obj is grayed out.""" 111 112 if not _settingsManager.getSetting('playSoundForState'): 113 return [] 114 115 filenames = super()._generateAvailability(obj, **args) 116 result = list(map(self._convertFilenameToIcon, filenames)) 117 if result: 118 return result 119 120 return [] 121 122 def _generateCheckedState(self, obj, **args): 123 """Returns an array of sounds indicating the checked state of obj.""" 124 125 if not _settingsManager.getSetting('playSoundForState'): 126 return [] 127 128 filenames = super()._generateCheckedState(obj, **args) 129 result = list(map(self._convertFilenameToIcon, filenames)) 130 if result: 131 return result 132 133 return [] 134 135 def _generateClickable(self, obj, **args): 136 """Returns an array of sounds indicating obj is clickable.""" 137 138 if not _settingsManager.getSetting('playSoundForState'): 139 return [] 140 141 filenames = super()._generateClickable(obj, **args) 142 result = list(map(self._convertFilenameToIcon, filenames)) 143 if result: 144 return result 145 146 return [] 147 148 def _generateExpandableState(self, obj, **args): 149 """Returns an array of sounds indicating the expanded state of obj.""" 150 151 if not _settingsManager.getSetting('playSoundForState'): 152 return [] 153 154 filenames = super()._generateExpandableState(obj, **args) 155 result = list(map(self._convertFilenameToIcon, filenames)) 156 if result: 157 return result 158 159 return [] 160 161 def _generateHasLongDesc(self, obj, **args): 162 """Returns an array of sounds indicating obj has a longdesc.""" 163 164 if not _settingsManager.getSetting('playSoundForState'): 165 return [] 166 167 filenames = super()._generateHasLongDesc(obj, **args) 168 result = list(map(self._convertFilenameToIcon, filenames)) 169 if result: 170 return result 171 172 return [] 173 174 def _generateMenuItemCheckedState(self, obj, **args): 175 """Returns an array of sounds indicating the checked state of obj.""" 176 177 if not _settingsManager.getSetting('playSoundForState'): 178 return [] 179 180 filenames = super()._generateMenuItemCheckedState(obj, **args) 181 result = list(map(self._convertFilenameToIcon, filenames)) 182 if result: 183 return result 184 185 return [] 186 187 def _generateMultiselectableState(self, obj, **args): 188 """Returns an array of sounds indicating obj is multiselectable.""" 189 190 if not _settingsManager.getSetting('playSoundForState'): 191 return [] 192 193 filenames = super()._generateMultiselectableState(obj, **args) 194 result = list(map(self._convertFilenameToIcon, filenames)) 195 if result: 196 return result 197 198 return [] 199 200 def _generateRadioState(self, obj, **args): 201 """Returns an array of sounds indicating the selected state of obj.""" 202 203 if not _settingsManager.getSetting('playSoundForState'): 204 return [] 205 206 filenames = super()._generateRadioState(obj, **args) 207 result = list(map(self._convertFilenameToIcon, filenames)) 208 if result: 209 return result 210 211 return [] 212 213 def _generateReadOnly(self, obj, **args): 214 """Returns an array of sounds indicating obj is read only.""" 215 216 if not _settingsManager.getSetting('playSoundForState'): 217 return [] 218 219 filenames = super()._generateReadOnly(obj, **args) 220 result = list(map(self._convertFilenameToIcon, filenames)) 221 if result: 222 return result 223 224 return [] 225 226 def _generateRequired(self, obj, **args): 227 """Returns an array of sounds indicating obj is required.""" 228 229 if not _settingsManager.getSetting('playSoundForState'): 230 return [] 231 232 filenames = super()._generateRequired(obj, **args) 233 result = list(map(self._convertFilenameToIcon, filenames)) 234 if result: 235 return result 236 237 return [] 238 239 def _generateSwitchState(self, obj, **args): 240 """Returns an array of sounds indicating the on/off state of obj.""" 241 242 if not _settingsManager.getSetting('playSoundForState'): 243 return [] 244 245 filenames = super()._generateSwitchState(obj, **args) 246 result = list(map(self._convertFilenameToIcon, filenames)) 247 if result: 248 return result 249 250 return [] 251 252 def _generateToggleState(self, obj, **args): 253 """Returns an array of sounds indicating the toggled state of obj.""" 254 255 if not _settingsManager.getSetting('playSoundForState'): 256 return [] 257 258 filenames = super()._generateToggleState(obj, **args) 259 result = list(map(self._convertFilenameToIcon, filenames)) 260 if result: 261 return result 262 263 return [] 264 265 def _generateVisitedState(self, obj, **args): 266 """Returns an array of sounds indicating the visited state of obj.""" 267 268 if not _settingsManager.getSetting('playSoundForState'): 269 return [] 270 271 if not args.get('mode', None): 272 args['mode'] = self._mode 273 274 args['stringType'] = 'visited' 275 if obj.getState().contains(pyatspi.STATE_VISITED): 276 filenames = [self._script.formatting.getString(**args)] 277 result = list(map(self._convertFilenameToIcon, filenames)) 278 if result: 279 return result 280 281 return [] 282 283 ##################################################################### 284 # # 285 # Value interface information # 286 # # 287 ##################################################################### 288 289 def _generatePercentage(self, obj, **args): 290 """Returns an array of sounds reflecting the percentage of obj.""" 291 292 if not _settingsManager.getSetting('playSoundForValue'): 293 return [] 294 295 percent = self._script.utilities.getValueAsPercent(obj) 296 if percent is None: 297 return [] 298 299 return [] 300 301 def _generateProgressBarValue(self, obj, **args): 302 """Returns an array of sounds representing the progress bar value.""" 303 304 if args.get('isProgressBarUpdate'): 305 if not self._shouldPresentProgressBarUpdate(obj, **args): 306 return [] 307 elif not _settingsManager.getSetting('playSoundForValue'): 308 return [] 309 310 percent = self._script.utilities.getValueAsPercent(obj) 311 if percent is None: 312 return [] 313 314 # To better indicate the progress completion. 315 if percent >= 99: 316 duration = 1 317 else: 318 duration = 0.075 319 320 # Reduce volume as pitch increases. 321 volumeMultiplier = 1 - (percent / 120) 322 323 # Adjusting so that the initial beeps are not too deep. 324 if percent < 7: 325 frequency = int(98 + percent * 5.4) 326 else: 327 frequency = int(percent * 22) 328 329 return [Tone(duration, frequency, volumeMultiplier, Tone.SINE_WAVE)] 330 331 def _getProgressBarUpdateInterval(self): 332 interval = _settingsManager.getSetting('progressBarBeepInterval') 333 if interval is None: 334 return super()._getProgressBarUpdateInterval() 335 336 return int(interval) 337 338 def _shouldPresentProgressBarUpdate(self, obj, **args): 339 if not _settingsManager.getSetting('beepProgressBarUpdates'): 340 return False 341 342 return super()._shouldPresentProgressBarUpdate(obj, **args) 343 344 ##################################################################### 345 # # 346 # Role and hierarchical information # 347 # # 348 ##################################################################### 349 350 def _generatePositionInSet(self, obj, **args): 351 """Returns an array of sounds reflecting the set position of obj.""" 352 353 if not _settingsManager.getSetting('playSoundForPositionInSet'): 354 return [] 355 356 position, setSize = self._script.utilities.getPositionAndSetSize(obj) 357 percent = int((position / setSize) * 100) 358 359 return [] 360 361 def _generateRoleName(self, obj, **args): 362 """Returns an array of sounds indicating the role of obj.""" 363 364 if not _settingsManager.getSetting('playSoundForRole'): 365 return [] 366 367 role = args.get('role', obj.getRole()) 368 filename = Atspi.role_get_name(role).replace(' ', '_') 369 result = self._convertFilenameToIcon(filename) 370 if result: 371 return [result] 372 373 return [] 374