psychopy 2025.1.1__py3-none-any.whl → 2025.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of psychopy might be problematic. Click here for more details.

Files changed (220) hide show
  1. psychopy/VERSION +1 -1
  2. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  3. psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
  4. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
  5. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
  6. psychopy/app/Resources/classic/add_many.png +0 -0
  7. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  8. psychopy/app/Resources/classic/devices.png +0 -0
  9. psychopy/app/Resources/classic/devices@2x.png +0 -0
  10. psychopy/app/Resources/classic/photometer.png +0 -0
  11. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  12. psychopy/app/Resources/dark/add_many.png +0 -0
  13. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  14. psychopy/app/Resources/dark/devices.png +0 -0
  15. psychopy/app/Resources/dark/devices@2x.png +0 -0
  16. psychopy/app/Resources/dark/photometer.png +0 -0
  17. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  18. psychopy/app/Resources/light/add_many.png +0 -0
  19. psychopy/app/Resources/light/add_many@2x.png +0 -0
  20. psychopy/app/Resources/light/devices.png +0 -0
  21. psychopy/app/Resources/light/devices@2x.png +0 -0
  22. psychopy/app/Resources/light/photometer.png +0 -0
  23. psychopy/app/Resources/light/photometer@2x.png +0 -0
  24. psychopy/app/_psychopyApp.py +35 -13
  25. psychopy/app/builder/builder.py +88 -35
  26. psychopy/app/builder/dialogs/__init__.py +69 -220
  27. psychopy/app/builder/dialogs/dlgsCode.py +29 -8
  28. psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
  29. psychopy/app/builder/validators.py +25 -17
  30. psychopy/app/coder/coder.py +12 -1
  31. psychopy/app/coder/repl.py +5 -2
  32. psychopy/app/colorpicker/__init__.py +1 -1
  33. psychopy/app/deviceManager/__init__.py +1 -0
  34. psychopy/app/deviceManager/addDialog.py +218 -0
  35. psychopy/app/deviceManager/dialog.py +185 -0
  36. psychopy/app/deviceManager/panel.py +191 -0
  37. psychopy/app/deviceManager/utils.py +60 -0
  38. psychopy/app/idle.py +7 -0
  39. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  40. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
  41. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  42. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
  43. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  44. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
  45. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  46. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
  47. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  48. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
  49. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  50. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
  51. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  52. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
  53. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  54. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
  55. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  56. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
  57. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  58. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
  59. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  60. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
  61. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  62. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
  63. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  64. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
  65. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  66. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
  67. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  68. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
  69. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  70. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
  71. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  72. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
  73. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  74. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
  75. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  76. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
  77. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  78. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
  79. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  80. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
  81. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  82. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
  83. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  84. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
  85. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  86. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
  87. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  88. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
  89. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  90. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
  91. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  92. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
  93. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  94. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
  95. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  96. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
  97. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  98. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
  99. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  100. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
  101. psychopy/app/plugin_manager/dialog.py +12 -3
  102. psychopy/app/plugin_manager/packageIndex.py +303 -0
  103. psychopy/app/plugin_manager/packages.py +203 -63
  104. psychopy/app/plugin_manager/plugins.py +120 -240
  105. psychopy/app/preferencesDlg.py +6 -1
  106. psychopy/app/psychopyApp.py +16 -4
  107. psychopy/app/runner/runner.py +10 -2
  108. psychopy/app/runner/scriptProcess.py +8 -3
  109. psychopy/app/stdout/stdOutRich.py +11 -4
  110. psychopy/app/themes/icons.py +3 -0
  111. psychopy/app/utils.py +61 -0
  112. psychopy/data/experiment.py +133 -23
  113. psychopy/data/routine.py +12 -0
  114. psychopy/data/staircase.py +42 -20
  115. psychopy/data/trial.py +20 -12
  116. psychopy/data/utils.py +42 -2
  117. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  118. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  119. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  120. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  121. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  122. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  123. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  124. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  125. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  126. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  127. psychopy/event.py +20 -15
  128. psychopy/experiment/_experiment.py +86 -10
  129. psychopy/experiment/components/__init__.py +3 -10
  130. psychopy/experiment/components/_base.py +9 -20
  131. psychopy/experiment/components/button/__init__.py +1 -1
  132. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  133. psychopy/experiment/components/camera/__init__.py +137 -359
  134. psychopy/experiment/components/keyboard/__init__.py +17 -24
  135. psychopy/experiment/components/microphone/__init__.py +61 -110
  136. psychopy/experiment/components/movie/__init__.py +2 -3
  137. psychopy/experiment/components/serialOut/__init__.py +192 -93
  138. psychopy/experiment/components/settings/__init__.py +45 -27
  139. psychopy/experiment/components/sound/__init__.py +82 -73
  140. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  141. psychopy/experiment/devices.py +303 -0
  142. psychopy/experiment/exports.py +20 -18
  143. psychopy/experiment/flow.py +7 -0
  144. psychopy/experiment/loops.py +47 -29
  145. psychopy/experiment/monitor.py +74 -0
  146. psychopy/experiment/params.py +48 -10
  147. psychopy/experiment/plugins.py +28 -108
  148. psychopy/experiment/py2js_transpiler.py +1 -1
  149. psychopy/experiment/routines/__init__.py +1 -1
  150. psychopy/experiment/routines/_base.py +59 -24
  151. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  152. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  153. psychopy/hardware/__init__.py +20 -57
  154. psychopy/hardware/button.py +15 -2
  155. psychopy/hardware/camera/__init__.py +2237 -1394
  156. psychopy/hardware/joystick/__init__.py +1 -1
  157. psychopy/hardware/keyboard.py +5 -8
  158. psychopy/hardware/listener.py +4 -1
  159. psychopy/hardware/manager.py +75 -35
  160. psychopy/hardware/microphone.py +52 -6
  161. psychopy/hardware/monitor.py +144 -0
  162. psychopy/hardware/photometer/__init__.py +156 -117
  163. psychopy/hardware/serialdevice.py +16 -2
  164. psychopy/hardware/soundsensor.py +4 -1
  165. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  166. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  167. psychopy/iohub/util/__init__.py +7 -8
  168. psychopy/localization/generateTranslationTemplate.py +208 -116
  169. psychopy/localization/messages.pot +4305 -3502
  170. psychopy/monitors/MonitorCenter.py +174 -74
  171. psychopy/plugins/__init__.py +6 -4
  172. psychopy/preferences/devices.py +80 -0
  173. psychopy/preferences/generateHints.py +2 -1
  174. psychopy/preferences/preferences.py +35 -11
  175. psychopy/scripts/psychopy-pkgutil.py +969 -0
  176. psychopy/scripts/psyexpCompile.py +1 -1
  177. psychopy/session.py +34 -38
  178. psychopy/sound/__init__.py +6 -260
  179. psychopy/sound/audioclip.py +164 -0
  180. psychopy/sound/backend_ptb.py +8 -0
  181. psychopy/sound/backend_pygame.py +10 -0
  182. psychopy/sound/backend_pysound.py +9 -0
  183. psychopy/sound/backends/__init__.py +0 -0
  184. psychopy/sound/microphone.py +3 -0
  185. psychopy/sound/sound.py +58 -0
  186. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  187. psychopy/tests/data/duplicateHeaders.csv +2 -0
  188. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  189. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  190. psychopy/tests/test_data/test_utils.py +5 -1
  191. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  192. psychopy/tests/test_hardware/test_ports.py +0 -12
  193. psychopy/tests/test_tools/test_stringtools.py +1 -1
  194. psychopy/tools/attributetools.py +12 -5
  195. psychopy/tools/fontmanager.py +17 -14
  196. psychopy/tools/movietools.py +43 -2
  197. psychopy/tools/stringtools.py +33 -8
  198. psychopy/tools/versionchooser.py +1 -1
  199. psychopy/validation/audio.py +5 -1
  200. psychopy/validation/visual.py +5 -1
  201. psychopy/visual/basevisual.py +8 -7
  202. psychopy/visual/circle.py +2 -2
  203. psychopy/visual/image.py +29 -109
  204. psychopy/visual/movies/__init__.py +1800 -313
  205. psychopy/visual/polygon.py +4 -0
  206. psychopy/visual/shape.py +2 -2
  207. psychopy/visual/window.py +34 -11
  208. psychopy/voicekey/__init__.py +41 -669
  209. psychopy/voicekey/labjack_vks.py +7 -48
  210. psychopy/voicekey/parallel_vks.py +7 -42
  211. psychopy/voicekey/vk_tools.py +114 -263
  212. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/METADATA +17 -11
  213. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/RECORD +216 -184
  214. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  215. psychopy/visual/movies/players/__init__.py +0 -62
  216. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  217. psychopy/voicekey/demo_vks.py +0 -12
  218. psychopy/voicekey/signal.py +0 -42
  219. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  220. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -143,7 +143,7 @@ def compileScript(infile=None, version=None, outfile=None):
143
143
  The experiment object used for generating the experiment script
144
144
  """
145
145
  # import PsychoPy experiment and write script with useVersion active
146
- from psychopy.app.builder import experiment
146
+ from psychopy import experiment
147
147
  # Check infile type
148
148
  if isinstance(infile, experiment.Experiment):
149
149
  thisExp = infile
psychopy/session.py CHANGED
@@ -256,6 +256,8 @@ class Session:
256
256
  ):
257
257
  # Store root and add to Python path
258
258
  self.root = Path(root)
259
+ if not self.root.is_absolute():
260
+ self.root = self.root.absolute()
259
261
  sys.path.insert(1, str(self.root))
260
262
  # store rest message
261
263
  self.restMsg = restMsg
@@ -962,44 +964,7 @@ class Session:
962
964
  f"Device names are not available for experiments added to Session directly as a "
963
965
  f".py file."
964
966
  )
965
- # get ready to store usages
966
- usages = {}
967
-
968
- def _process(name, emt):
969
- """
970
- Process an element (Component or Routine) for device names and append them to the
971
- usages dict.
972
-
973
- Parameters
974
- ----------
975
- name : str
976
- Name of this element in Builder
977
- emt : Component or Routine
978
- Element to process
979
- """
980
- # if we have a device name for this element...
981
- if "deviceLabel" in emt.params:
982
- # get init value so it lines up with boilerplate code
983
- inits = experiment.getInitVals(emt.params)
984
- # get value
985
- deviceName = inits['deviceLabel'].val
986
- # if deviceName exists from other elements, add usage to it
987
- if deviceName in usages:
988
- usages[deviceName].append(name)
989
- else:
990
- usages[deviceName] = [name]
991
-
992
- # iterate through routines
993
- for rtName, rt in exp.routines.items():
994
- if isinstance(rt, experiment.routines.BaseStandaloneRoutine):
995
- # for standalone routines, get device names from params
996
- _process(rtName, rt)
997
- else:
998
- # for regular routines, get device names from each component
999
- for comp in rt:
1000
- _process(comp.name, comp)
1001
-
1002
- return list(usages)
967
+ return exp.getRequiredDeviceNames()
1003
968
 
1004
969
  def runExperiment(self, key, expInfo=None, blocking=True):
1005
970
  """
@@ -1264,6 +1229,13 @@ class Session:
1264
1229
 
1265
1230
  # set ExperimentHandler status to PAUSED
1266
1231
  self.currentExperiment.pause()
1232
+ # update Liaison if needed
1233
+ if self.liaison is not None:
1234
+ self.sendToLiaison({
1235
+ 'type': "experiment_status",
1236
+ 'name': self.currentExperiment.name,
1237
+ 'status': self.currentExperiment.status,
1238
+ })
1267
1239
 
1268
1240
  return True
1269
1241
 
@@ -1285,6 +1257,13 @@ class Session:
1285
1257
  return False
1286
1258
  # set ExperimentHandler status to STARTED
1287
1259
  self.currentExperiment.resume()
1260
+ # update Liaison if needed
1261
+ if self.liaison is not None:
1262
+ self.sendToLiaison({
1263
+ 'type': "experiment_status",
1264
+ 'name': self.currentExperiment.name,
1265
+ 'status': self.currentExperiment.status,
1266
+ })
1288
1267
 
1289
1268
  return True
1290
1269
 
@@ -1305,8 +1284,25 @@ class Session:
1305
1284
  )
1306
1285
  return False
1307
1286
  self.currentExperiment.stop()
1287
+ # update Liaison if needed
1288
+ if self.liaison is not None:
1289
+ self.sendToLiaison({
1290
+ 'type': "experiment_status",
1291
+ 'name': self.currentExperiment.name,
1292
+ 'status': self.currentExperiment.status,
1293
+ })
1308
1294
 
1309
1295
  return True
1296
+
1297
+ def next(self):
1298
+ """
1299
+ Move on to either the next trial (if in a trials loop) or the next Routine.
1300
+ """
1301
+ # return if there's no current experiment
1302
+ if self.currentExperiment is None:
1303
+ return
1304
+ # skip trials in current loop
1305
+ return self.currentExperiment.next()
1310
1306
 
1311
1307
  def skipTrials(self, n=1):
1312
1308
  """
@@ -34,271 +34,17 @@ After importing sound, the sound lib and driver being used will be stored as::
34
34
  # Distributed under the terms of the GNU General Public License (GPL).
35
35
 
36
36
  import sys
37
- import os
38
- import traceback
39
- from psychopy import logging, prefs, constants
40
- from psychopy.tools import systemtools
41
- from .exceptions import DependencyError, SoundFormatError
42
37
  from .audiodevice import *
43
38
  from .audioclip import * # import objects related to AudioClip
44
- from . import microphone
39
+ from . import microphone, sound
40
+ from .sound import Sound
45
41
 
46
- __all__ = ["microphone"]
42
+ __all__ = [
43
+ "microphone",
44
+ "Sound"
45
+ ]
47
46
 
48
- # # import transcription if possible
49
- # try:
50
- # from .transcribe import * # import transcription engine stuff
51
- # except ImportError as err:
52
- # formatted_tb = ''.join(
53
- # traceback.format_exception(type(err), err, err.__traceback__))
54
- # logging.error(
55
- # "Failed to import psychopy.sound.transcribe. Transcription will not be"
56
- # "possible on this machine. For details see stack trace below:\n"
57
- # f"{formatted_tb}")
58
47
 
59
48
  # used to check if we are on 64-bit Python
60
49
  bits32 = sys.maxsize == 2 ** 32
61
50
 
62
- # Globals for the sound library. We can only load one audio library at a time,
63
- # so once these values are populated they cannot be changed without restarting
64
- # Python.
65
- pyoSndServer = None
66
- Sound = None
67
- audioLib = None
68
- audioDriver = None
69
- backend = None
70
-
71
- # These are the names that can be used in the prefs to specifiy audio libraries.
72
- # The available libraries are hard-coded at this point until we can overhaul
73
- # the sound library to be more modular.
74
- _audioLibs = ['ptb', 'sounddevice', 'pyo', 'pysoundcard', 'pygame']
75
- failed = [] # keep track of audio libs that failed to load
76
-
77
- # check if this is being imported on Travis/Github (has no audio card)
78
- if systemtools.isVM_CI():
79
- # for sounddevice we built in some VM protection but not in pyo
80
- prefs.hardware['audioLib'] = ['ptb', 'sounddevice']
81
-
82
- # ensure that the value for `audioLib` is a list
83
- if isinstance(prefs.hardware['audioLib'], str):
84
- prefs.hardware['audioLib'] = [prefs.hardware['audioLib']]
85
-
86
- thisLibName = None # name of the library we are trying to load
87
-
88
- # selection and fallback mechanism for audio libraries
89
- for thisLibName in prefs.hardware['audioLib']:
90
- # Tell the user we are trying to load the specifeid audio library
91
- logging.info(f"Trying to load audio library: {thisLibName}")
92
-
93
- # Iterate over the list of audioLibs and try to load the first one that
94
- # is supported. If none are supported, load PTB as a fallback. If PTB isn't
95
- # installed, raise an error.
96
- thisLibName = thisLibName.lower()
97
-
98
- # lowercased list of valid audio libraries for safe comparisons
99
- validLibs = [libName.lower() for libName in _audioLibs]
100
-
101
- # check if `thisLibName` is a valid audio library
102
- if thisLibName not in validLibs:
103
- failed.append(thisLibName)
104
- logging.warning(f"Invalid audioLib pref: {thisLibName}. "
105
- f"Valid options are: {_audioLibs}")
106
- continue
107
-
108
- # select the backend and set the Sound class
109
- if thisLibName == 'ptb':
110
- # The Psychtoolbox backend is preferred, provides the best performance
111
- # and is the only one that supports low-latency scheduling. If no other
112
- # audio backend can be loaded, we will use PTB.
113
- if not bits32:
114
- try:
115
- from . import backend_ptb as backend
116
- Sound = backend.SoundPTB
117
- audioDriver = backend.audioDriver
118
- except Exception as err:
119
- failed.append(thisLibName)
120
- continue
121
- else:
122
- break
123
- else:
124
- logging.warning("PTB backend is not supported on 32-bit Python. "
125
- "Trying another backend...")
126
- continue
127
- elif thisLibName == 'pyo':
128
- # pyo is a wrapper around PortAudio, which is a cross-platform audio
129
- # library. It is the recommended backend for Windows and Linux.
130
- try:
131
- # Caution: even import failed inside, we still get a module object.
132
- # This is not the case for other backends and may not be desired.
133
- from . import backend_pyo as backend
134
- Sound = backend.SoundPyo
135
- pyoSndServer = backend.pyoSndServer
136
- audioDriver = backend.audioDriver
137
- except Exception:
138
- failed.append(thisLibName)
139
- continue
140
- else:
141
- break
142
- elif thisLibName == 'sounddevice':
143
- # sounddevice is a wrapper around PortAudio, which is a cross-platform
144
- # audio library. It is the recommended backend for Windows and Linux.
145
- try:
146
- # Caution: even import failed inside, we still get a module object.
147
- # This is not the case for other backends and may not be desired.
148
- from . import backend_sounddevice as backend
149
- Sound = backend.SoundDeviceSound
150
- except Exception:
151
- failed.append(thisLibName)
152
- continue
153
- else:
154
- break
155
- elif thisLibName == 'pygame':
156
- # pygame is a cross-platform audio library. It is no longer supported by
157
- # PsychoPy, but we keep it here for backwards compatibility until
158
- # something breaks.
159
- try:
160
- from . import backend_pygame as backend
161
- Sound = backend.SoundPygame
162
- except Exception:
163
- failed.append(thisLibName)
164
- continue
165
- else:
166
- break
167
- elif thisLibName == 'pysoundcard':
168
- # pysoundcard is a wrapper around PortAudio, which is a cross-platform
169
- # audio library.
170
- try:
171
- from . import backend_pysound as backend
172
- Sound = backend.SoundPySoundCard
173
- except Exception:
174
- failed.append(thisLibName)
175
- continue
176
- else:
177
- break
178
- else:
179
- # Catch-all for invalid audioLib prefs.
180
- msg = ("audioLib pref should be one of {!r}, not {!r}"
181
- .format(_audioLibs, thisLibName))
182
- raise ValueError(msg)
183
- else:
184
- # if we get here, there is no audioLib that is supported, try for PTB
185
- msg = ("Failed to load any of the audioLibs: {!r}. Falling back to "
186
- "PsychToolbox ('ptb') backend for sound. Be sure to add 'ptb' to "
187
- "preferences to avoid seeing this message again.".format(failed))
188
- logging.error(msg)
189
- try:
190
- from . import backend_ptb as backend
191
- Sound = backend.SoundPTB
192
- audioDriver = backend.audioDriver
193
- except Exception:
194
- failed.append(thisLibName)
195
-
196
-
197
- # we successfully loaded a backend if `Sound` is not None
198
- if Sound is not None:
199
- audioLib = thisLibName
200
- init = backend.init
201
- getDevices = None
202
- if audioLib != 'ptb' and hasattr(backend, 'getDevices'):
203
- getDevices = backend.getDevices
204
- else:
205
- import psychopy.hardware.speaker as speaker
206
-
207
- def _getDevices(*args, **kwargs): # for matching function signiture
208
- allDevices = speaker.SpeakerDevice.getAvailableDevices()
209
- return [dev['name'] for dev in allDevices]
210
-
211
- getDevices = _getDevices
212
-
213
- logging.info('sound is using audioLib: %s' % audioLib)
214
- else:
215
- # if we get here, there is no audioLib that is supported
216
- logging.error(
217
- "No audioLib could be loaded. Tried: {}\n Check whether the necessary "
218
- "audioLibs are installed.".format(prefs.hardware['audioLib']))
219
-
220
- # warn the user
221
- if audioLib is not None:
222
- if audioLib.lower() != 'ptb':
223
- # Could be running PTB, just aren't?
224
- logging.warning("We strongly recommend you activate the PTB sound "
225
- "engine in PsychoPy prefs as the preferred audio "
226
- "engine. Its timing is vastly superior. Your prefs "
227
- "are currently set to use {} (in that order)."
228
- .format(prefs.hardware['audioLib']))
229
-
230
-
231
- # function to set the device (if current lib allows it)
232
- def setDevice(dev, kind=None):
233
- """Sets the device to be used for new streams being created.
234
-
235
- Parameters
236
- ----------
237
- dev: str or dict
238
- Name of the device to be used (name, index or sounddevice.device)
239
- kind: str
240
- One of [None, 'output', 'input']
241
-
242
- """
243
- if dev is None:
244
- # if given None, do nothing
245
- return
246
-
247
- global backend # pull from module namespace
248
- if not hasattr(backend, 'defaultOutput'):
249
- raise IOError("Attempting to SetDevice (audio) but not supported by "
250
- "the current audio library ({!r})".format(audioLib))
251
-
252
- if hasattr(dev, 'name'):
253
- dev = dev['name']
254
-
255
- if kind is None:
256
- backend.defaultInput = backend.defaultOutput = dev
257
- elif kind == 'input':
258
- backend.defaultInput = dev
259
- elif kind == 'output':
260
- backend.defaultOutput = dev
261
- else:
262
- if systemtools.isVM_CI(): # no audio device on CI, ignore
263
- return
264
- else:
265
- raise TypeError("`kind` should be one of [None, 'output', 'input'] "
266
- "not {!r}".format(kind))
267
-
268
-
269
- # Set the device according to user prefs (if current lib allows it)
270
- deviceNames = []
271
- if backend is None:
272
- raise ImportError("None of the audio library backends could be imported. "
273
- "Tried: {}\n Check whether the necessary audioLibs are "
274
- "installed and can be imported successfully."
275
- .format(prefs.hardware['audioLib']))
276
- elif hasattr(backend, 'defaultOutput'):
277
- pref = prefs.hardware['audioDevice']
278
- # is it a list or a simple string?
279
- if isinstance(pref, list):
280
- # multiple options so use zeroth
281
- dev = pref[0]
282
- else:
283
- # a single option
284
- dev = pref
285
- # is it simply "default" (do nothing)
286
- if dev == 'default' or systemtools.isVM_CI():
287
- pass # do nothing
288
- elif getDevices is not None:
289
- devices = getDevices(kind='output')
290
- if type(devices) is dict:
291
- deviceNames = sorted(devices.keys())
292
- else:
293
- deviceNames = sorted(devices)
294
- if dev not in devices:
295
- logging.warn(
296
- u"Requested audio device '{}' that is not available on "
297
- "this hardware. The 'audioDevice' preference should be one of "
298
- "{}".format(dev, deviceNames))
299
- else:
300
- setDevice(dev, kind='output')
301
-
302
-
303
- if __name__ == "__main__":
304
- pass
@@ -812,7 +812,165 @@ class AudioClip:
812
812
  self._sampleRateHz = targetSampleRateHz
813
813
 
814
814
  return self
815
+
816
+ def pad(self, direction='start', duration=1.0, units='seconds'):
817
+ """Pad the audio clip with silence at the start or end.
818
+
819
+ This method will modify the audio clip inplace, adding samples of
820
+ silence to the specified location.
821
+
822
+ Parameters
823
+ ----------
824
+ direction : str
825
+ Where to add the padding. Can be 'start' or 'end'. Default is 'start'.
826
+ duration : float or int
827
+ Duration of the padding in seconds. Default is `1.0`.
828
+ units : str
829
+ Units to interpret the `duration` parameter as. Can be 'seconds' or
830
+ 'samples'. Default is 'seconds'.
831
+
832
+ Returns
833
+ -------
834
+ AudioClip
835
+ This audio clip object with padding added at the specified location.
836
+
837
+ """
838
+ # convert duration to samples
839
+ if units == 'seconds':
840
+ padSamples = int(duration * self.sampleRateHz)
841
+ elif units == 'samples':
842
+ padSamples = int(duration)
843
+ else:
844
+ raise ValueError(
845
+ "Invalid value for `units`. Must be 'seconds' or 'samples'.")
846
+
847
+ # create padding samples
848
+ padding = np.zeros((padSamples, self.channels), dtype=np.float32)
849
+
850
+ if direction == 'start':
851
+ self._samples = np.vstack((padding, self._samples))
852
+ elif direction == 'end':
853
+ self._samples = np.vstack((self._samples, padding))
854
+ else:
855
+ raise ValueError(
856
+ "Invalid value for `direction`. Must be 'start' or 'end'.")
857
+
858
+ # ensure the samples are contiguous and right dtype
859
+ self._samples = np.ascontiguousarray(self._samples, dtype=np.float32)
860
+
861
+ # recompute the duration of the new clip
862
+ self._duration = len(self.samples) / float(self.sampleRateHz)
863
+
864
+ return self
865
+
866
+ def padded(self, direction='start', duration=1.0, units='seconds'):
867
+ """Return a new audio clip with padding added at the start or end.
868
+
869
+ This method will return a new audio clip with samples of silence added
870
+ to the specified location. The original audio clip will not be modified.
871
+
872
+ Parameters
873
+ ----------
874
+ direction : str
875
+ Where to add the padding. Can be 'start' or 'end'. Default is 'start'.
876
+ duration : float or int
877
+ Duration of the padding in seconds. Default is `1.0`.
878
+ units : str
879
+ Units to interpret the `duration` parameter as. Can be 'seconds' or
880
+ 'samples'. Default is 'seconds'.
881
+
882
+ Returns
883
+ -------
884
+ AudioClip
885
+ A new audio clip with padding added at the specified location.
886
+
887
+ """
888
+ # create a copy of the audio clip
889
+ newClip = self.copy()
890
+
891
+ # pad the copy
892
+ return newClip.pad(direction, duration, units)
893
+
894
+ def trim(self, direction='start', duration=1.0, units='seconds'):
895
+ """Trim the audio clip by removing samples from the start or end.
896
+
897
+ This method will modify the audio clip inplace, trimming off samples
898
+ from the specfied direction.
815
899
 
900
+ Parameters
901
+ ----------
902
+ direction : str
903
+ Where to remove audio samples from. Can be 'start' or 'end'. Default
904
+ is 'start'.
905
+ duration : float or int
906
+ How many samples to remove from the start or end of the audio clip.
907
+ units : str
908
+ Units to interpret the `duration` parameter as. Can be 'seconds' or
909
+ 'samples'. Default is 'seconds'.
910
+
911
+ Returns
912
+ -------
913
+ AudioClip
914
+ This audio clip object with padding removed at the specified
915
+ location.
916
+
917
+ """
918
+ # convert duration to samples
919
+ if units == 'seconds':
920
+ trimSamples = int(duration * self.sampleRateHz)
921
+ elif units == 'samples':
922
+ trimSamples = int(duration)
923
+ else:
924
+ raise ValueError(
925
+ "Invalid value for `units`. Must be 'seconds' or 'samples'.")
926
+
927
+ if direction == 'start':
928
+ self._samples = self._samples[trimSamples:, :]
929
+ elif direction == 'end':
930
+ self._samples = self._samples[:-trimSamples, :]
931
+ else:
932
+ raise ValueError(
933
+ "Invalid value for `direction`. Must be 'start' or 'end'.")
934
+
935
+ # ensure the samples are contiguous and right dtype
936
+ self._samples = np.ascontiguousarray(self._samples, dtype=np.float32)
937
+
938
+ # recompute the duration of the new clip
939
+ self._duration = len(self.samples) / float(self.sampleRateHz)
940
+
941
+ return self
942
+
943
+ def trimmed(self, direction='start', duration=1.0, units='seconds'):
944
+ """Return a new audio clip with samples removed from the start or end.
945
+
946
+ This method will return a new audio clip with samples of silence removed
947
+ from the specified location. The original audio clip will not be
948
+ modified.
949
+
950
+ Parameters
951
+ ----------
952
+ direction : str
953
+ Where to remove audio samples from. Can be 'start' or 'end'. Default
954
+ is 'start'.
955
+ duration : float or int
956
+ How many samples to remove from the start or end of the audio clip.
957
+ units : str
958
+ Units to interpret the `duration` parameter as. Can be 'seconds' or
959
+ 'samples'. Default is 'seconds'.
960
+
961
+ Returns
962
+ -------
963
+ AudioClip
964
+ A new audio clip with padding removed at the specified location.
965
+
966
+ """
967
+ # create a copy of the audio clip
968
+ newClip = self.copy()
969
+
970
+ # trim the copy
971
+ return newClip.trim(direction, duration, units)
972
+
973
+
816
974
  # --------------------------------------------------------------------------
817
975
  # Audio analysis methods
818
976
  #
@@ -884,6 +1042,12 @@ class AudioClip:
884
1042
  # recompute duration after updating sample rate
885
1043
  self._duration = len(self._samples) / float(self._sampleRateHz)
886
1044
 
1045
+ @property
1046
+ def totalSamples(self):
1047
+ """Total number of audio samples stored in this object (`int`).
1048
+ """
1049
+ return self._samples.shape[0] # number of rows
1050
+
887
1051
  @property
888
1052
  def duration(self):
889
1053
  """The duration of the audio in seconds (`float`).
@@ -34,6 +34,11 @@ except Exception:
34
34
 
35
35
  import numpy as np
36
36
 
37
+ __all__ = [
38
+ "SoundPTB",
39
+ "Sound"
40
+ ]
41
+
37
42
 
38
43
  defaultLatencyClass = 1
39
44
  # suggestedLatency = 0.005 ## Not currently used. Keep < 1 scr refresh
@@ -396,3 +401,6 @@ class SoundPTB(_SoundBase):
396
401
  self.__dict__['track'] = None
397
402
  else:
398
403
  self.__dict__['track'] = weakref.ref(track)
404
+
405
+
406
+ Sound = SoundPTB
@@ -19,6 +19,13 @@ except ImportError as err:
19
19
  raise DependencyError(repr(err))
20
20
 
21
21
 
22
+
23
+ __all__ = [
24
+ "SoundPygame",
25
+ "Sound"
26
+ ]
27
+
28
+
22
29
  def getDevices(kind=None):
23
30
  """Get audio playback and recording devices via the backend's audio API.
24
31
 
@@ -311,3 +318,6 @@ class SoundPygame(_SoundBase):
311
318
  thisArray = ((thisArray + 1) * 2**7).astype(numpy.uint8)
312
319
 
313
320
  self._snd = sndarray.make_sound(thisArray)
321
+
322
+
323
+ Sound = SoundPygame
@@ -21,6 +21,12 @@ from os import path
21
21
  import weakref
22
22
 
23
23
 
24
+ __all__ = [
25
+ "SoundPySoundCard",
26
+ "Sound"
27
+ ]
28
+
29
+
24
30
  def init(rate=44100, stereo=True, buffer=128):
25
31
  pass
26
32
  # for compatibility with other backends but not needed
@@ -313,3 +319,6 @@ class SoundPySoundCard(_SoundBase):
313
319
  def __del__(self):
314
320
  if hasattr(self, "_stream"):
315
321
  self._stream.close()
322
+
323
+
324
+ Sound = SoundPySoundCard
File without changes
@@ -347,6 +347,9 @@ class Microphone:
347
347
  def getRecording(self):
348
348
  return self.device.getRecording()
349
349
 
350
+ def getCurrentVolume(self):
351
+ return self.device.getCurrentVolume()
352
+
350
353
 
351
354
  if __name__ == "__main__":
352
355
  pass